# Sisk Framework documentation > Sisk is a lightweight, agnostic and robust .NET web development framework. This file contains the complete Sisk documentation (Deutsch). The API reference is not included; read https://docs.sisk-framework.org/api/index.md for the type index. --- # Erste Schritte Source: https://docs.sisk-framework.org/de/docs/getting-started.html Willkommen zur Sisk-Dokumentation! Sisk ist ein quelloffenes leichtgewichtiges HTTP‑Framework für .NET. Du kannst es verwenden, um einen eigenständigen Web‑Service zu erstellen, ein HTTP‑Modul in eine bestehende Anwendung einzubetten oder einen Service hinter einem Reverse‑Proxy mit nur der benötigten Konfiguration zu betreiben. Die Werte von Sisk umfassen Code‑Transparenz, Modularität, Leistung und Skalierbarkeit. Es kann verschiedene Anwendungsstile handhaben, darunter RESTful‑APIs, JSON‑RPC‑Dienste, WebSockets, Server‑Sent‑Events und das Bereitstellen statischer Dateien. Seine Hauptfunktionen umfassen: | Ressource | Beschreibung | | --------- | ------------ | | [Routing](https://docs.sisk-framework.org/de/docs/fundamentals/routing.md) | Ein Pfadrouter, der Präfixe, benutzerdefinierte Methoden, Pfadvariablen, Wertkonverter und mehr unterstützt. | | [Request Handlers](https://docs.sisk-framework.org/de/docs/fundamentals/request-handlers.md) | Auch bekannt als *Middlewares*, bietet eine Schnittstelle zum Erstellen eigener Request‑Handler, die vor oder nach einer Aktion mit der Anfrage arbeiten. | | [Compression](https://docs.sisk-framework.org/de/docs/fundamentals/responses.md#gzip-deflate-and-brotli-compression) | Komprimiere deine Antwortinhalte einfach mit Sisk. | | [Web sockets](https://docs.sisk-framework.org/de/docs/features/websockets.md) | Stellt Routen bereit, die vollständige WebSockets akzeptieren, zum Lesen und Schreiben zum Client. | | [Server-sent events](https://docs.sisk-framework.org/de/docs/features/server-sent-events.md) | Ermöglicht das Senden von Serverereignissen an Clients, die das SSE‑Protokoll unterstützen. | | [Logging](https://docs.sisk-framework.org/de/docs/features/logging.md) | Vereinfachtes Logging. Protokolliere Fehler, Zugriffe, definiere rotierende Logs nach Größe, mehrere Ausgabeströme für dasselbe Log und mehr. | | [Multi-host](https://docs.sisk-framework.org/de/docs/advanced/multi-host-setup.md) | Betreibe einen HTTP‑Server für mehrere Ports, wobei jeder Port seinen eigenen Router und jeder Router seine eigene Anwendung hat. | | [Server handlers](https://docs.sisk-framework.org/de/docs/advanced/http-server-handlers.md) | Erweitere deine eigene Implementierung des HTTP‑Servers. Passe ihn mit Erweiterungen, Verbesserungen und neuen Funktionen an. | ## Erste Schritte Sisk kann in jeder .NET‑Umgebung ausgeführt werden. In diesem Leitfaden zeigen wir dir, wie du eine Sisk‑Anwendung mit .NET erstellst. Falls du das SDK noch nicht installiert hast, lade es bitte von [hier](https://dotnet.microsoft.com/en-us/download/dotnet/7.0) herunter. In diesem Tutorial behandeln wir, wie man eine Projektstruktur erstellt, eine Anfrage empfängt, einen URL‑Parameter erhält und eine Antwort sendet. Dieser Leitfaden konzentriert sich darauf, einen einfachen Server mit C# zu bauen. Du kannst jedoch auch deine bevorzugte Programmiersprache verwenden. > [!NOTE] > Vielleicht bist du an einem Quick‑Start‑Projekt interessiert. Sieh dir [dieses Repository](https://github.com/sisk-http/quickstart) für weitere Informationen an. ## Erstellen eines Projekts Nennen wir unser Projekt „My Sisk Application“. Sobald .NET eingerichtet ist, kannst du dein Projekt mit dem folgenden Befehl erstellen: ```bash dotnet new console -n my-sisk-application ``` Navigiere anschließend in dein Projektverzeichnis und installiere Sisk mit dem .NET‑Utility‑Tool: ```bash cd my-sisk-application dotnet add package Sisk.HttpServer ``` Weitere Installationsmöglichkeiten für Sisk in deinem Projekt findest du [hier](https://www.nuget.org/packages/Sisk.HttpServer/). Jetzt erstellen wir eine Instanz unseres HTTP‑Servers. In diesem Beispiel konfigurieren wir ihn, um auf Port 5000 zu lauschen. ## Aufbau des HTTP‑Servers Sisk ermöglicht es dir, deine Anwendung Schritt für Schritt manuell zu bauen, da es zum HttpServer‑Objekt routet. Das ist jedoch für die meisten Projekte nicht sehr praktisch. Daher können wir die Builder‑Methode verwenden, die das Aufsetzen unserer App erleichtert. ```csharp {title="Program.cs"} class Program { static async Task Main(string[] args) { using var app = HttpServer.CreateBuilder() .UseListeningPort("http://localhost:5000/") .Build(); app.Router.MapGet("/", request => { return new HttpResponse() { Status = 200, Content = new StringContent("Hello, world!") }; }); await app.StartAsync(); } } ``` Es ist wichtig, jede wesentliche Komponente von Sisk zu verstehen. Später in diesem Dokument erfährst du mehr darüber, wie Sisk funktioniert. ## Manuelle (erweiterte) Einrichtung Du kannst lernen, wie jeder Sisk‑Mechanismus funktioniert, in [diesem Abschnitt](https://docs.sisk-framework.org/de/docs/advanced/manual-setup.md) der Dokumentation, der das Verhalten und die Beziehungen zwischen HttpServer, Router, ListeningPort und anderen Komponenten erklärt. --- # Installation Source: https://docs.sisk-framework.org/de/docs/installing.html Sie können Sisk über Nuget, dotnet cli oder [andere Optionen](https://www.nuget.org/packages/Sisk.HttpServer/) installieren. Sie können Ihre Sisk-Umgebung leicht einrichten, indem Sie diesen Befehl in Ihrer Entwicklerkonsole ausführen: ```sh dotnet add package Sisk.HttpServer ``` Dieser Befehl installiert die neueste Version von Sisk in Ihrem Projekt. --- # Native AOT-Unterstützung Source: https://docs.sisk-framework.org/de/docs/native-aot.html [.NET Native AOT](https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/) ermöglicht die Veröffentlichung von nativen .NET-Anwendungen, die selbstständig sind und nicht die .NET-Laufzeit auf dem Zielhost benötigen. Zusätzlich bietet Native AOT Vorteile wie: - Erheblich kleinere Anwendungen - Wesentlich schnellere Initialisierung - Geringeren Speicherbedarf Das Sisk Framework ermöglicht aufgrund seiner expliziten Natur die Verwendung von Native AOT für fast alle seine Funktionen, ohne dass eine Überarbeitung des Quellcodes erforderlich ist, um es an Native AOT anzupassen. ## Nicht unterstützte Funktionen Allerdings verwendet Sisk Reflexion, wenn auch minimal, für einige Funktionen. Die folgenden Funktionen sind möglicherweise teilweise verfügbar oder während der nativen Codeausführung ganz nicht verfügbar: - [Automatisches Scannen von Modulen](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.AutoScanModules.md) des Routers: Diese Ressource scannet die im ausführenden Assembly eingebetteten Typen und registriert die Typen, die [Router-Module](https://docs.sisk-framework.org/de/docs/fundamentals/routing.md) sind. Diese Ressource benötigt Typen, die während des Assembly-Trimming ausgeschlossen werden können. Alle anderen Funktionen sind mit AOT in Sisk kompatibel. Es ist üblich, eine oder andere Methode zu finden, die eine AOT-Warnung ausgibt, aber dieselbe, wenn sie hier nicht erwähnt wird, hat eine Überladung, die das Übergeben eines Typs, Parameters oder Typinformationen anzeigt, die dem AOT-Compiler helfen, das Objekt zu kompilieren. --- # Bereitstellung Ihrer Sisk-Anwendung Source: https://docs.sisk-framework.org/de/docs/deploying.html Der Prozess der Bereitstellung einer Sisk-Anwendung besteht darin, Ihr Projekt in die Produktion zu veröffentlichen. Obwohl der Prozess relativ einfach ist, ist es erwähnenswert, Details zu beachten, die für die Sicherheit und Stabilität der Infrastruktur der Bereitstellung tödlich sein können. Idealerweise sollten Sie bereit sein, Ihre Anwendung in die Cloud zu deployen, nachdem Sie alle möglichen Tests durchgeführt haben, um Ihre Anwendung bereit zu machen. ## Veröffentlichen Ihrer App Das Veröffentlichen Ihrer Sisk-Anwendung oder eines Dienstes bedeutet, Binaries zu generieren, die für die Produktion bereit und optimiert sind. In diesem Beispiel werden wir die Binaries für die Produktion kompilieren, um auf einer Maschine zu laufen, die die .NET-Laufzeit auf der Maschine installiert hat. Sie benötigen die .NET-SDK auf Ihrem Computer installiert, um Ihre App zu erstellen, und die .NET-Laufzeit auf dem Zielserver, um Ihre App auszuführen. Sie können erfahren, wie Sie die .NET-Laufzeit auf Ihrem Linux-Server [hier](https://learn.microsoft.com/en-us/dotnet/core/install/linux), [Windows](https://learn.microsoft.com/en-us/dotnet/core/install/windows?tabs=net70) und [Mac OS](https://learn.microsoft.com/en-us/dotnet/core/install/macos) installieren. Im Ordner, in dem sich Ihr Projekt befindet, öffnen Sie ein Terminal und verwenden den .NET-Veröffentlichungsbefehl: ```shell $ dotnet publish -r linux-x64 -c Release ``` Dies generiert Ihre Binaries innerhalb von `bin/Release/publish/linux-x64`. > [!NOTE] > Wenn Ihre App mit dem Sisk.ServiceProvider-Paket läuft, sollten Sie Ihre `service-config.json` in Ihren Hostserver kopieren, zusammen mit allen Binaries, die von `dotnet publish` generiert werden. > Sie können die Datei vor konfigurieren, mit Umgebungsvariablen, Lauscher-Ports und Hosts und zusätzlichen Serverkonfigurationen. Der nächste Schritt ist, diese Dateien auf den Server zu übertragen, auf dem Ihre Anwendung gehostet wird. Danach geben Sie Ausführungsrechte für Ihre Binärdatei. In diesem Fall nehmen wir an, dass unser Projektname "my-app" ist: ```shell $ cd /home/htdocs $ chmod +x my-app $ ./my-app ``` Nach dem Ausführen Ihrer Anwendung prüfen Sie, ob sie Fehlermeldungen produziert. Wenn sie keine produziert, bedeutet dies, dass Ihre Anwendung läuft. An diesem Punkt ist es wahrscheinlich nicht möglich, auf Ihre Anwendung von außerhalb Ihres Servers zuzugreifen, da Zugriffsregeln wie Firewall nicht konfiguriert sind. Wir werden dies in den nächsten Schritten berücksichtigen. Sie sollten die Adresse des virtuellen Hosts haben, auf dem Ihre Anwendung läuft. Dies wird manuell in der Anwendung festgelegt und hängt davon ab, wie Sie Ihren Sisk-Dienst instanziieren. Wenn Sie **nicht** das Sisk.ServiceProvider-Paket verwenden, sollten Sie es finden, wo Sie Ihre HttpServer-Instanz definiert haben: ```cs HttpServer server = HttpServer.Emit(5000, out HttpServerConfiguration config, out var host, out var router); // sisk sollte auf http://localhost:5000/ lauschen ``` Manuelle Zuweisung eines Lauschers: ```cs config.ListeningHosts.Add(new ListeningHost("https://localhost:5000/", router)); ``` Oder wenn Sie das Sisk.ServiceProvider-Paket verwenden, in Ihrer `service-config.json`: ```json { "Server": { }, "ListeningHost": { "Ports": [ "http://localhost:5000/" ] } } ``` Von hier aus können wir einen Reverse-Proxy erstellen, um Ihren Dienst zu lauschen und den Datenverkehr über das offene Netzwerk verfügbar zu machen. ## Proxying Ihrer Anwendung Das Proxying Ihres Dienstes bedeutet, dass Sie Ihren Sisk-Dienst nicht direkt einem externen Netzwerk aussetzen. Diese Praxis ist sehr häufig bei Server-Bereitstellungen, da: - Sie damit ein SSL-Zertifikat in Ihrer Anwendung zuordnen können; - Sie Zugriffsregeln vor dem Zugriff auf den Dienst erstellen und Überlastungen vermeiden können; - Sie die Bandbreite und Anfragegrenzen kontrollieren können; - Sie Lastenausgleich für Ihre Anwendung trennen können; - Sie Sicherheitsschäden an der fehlgeschlagenen Infrastruktur verhindern können. Sie können Ihre Anwendung durch einen Reverse-Proxy wie [Nginx](https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/linux-nginx?view=aspnetcore-7.0&tabs=linux-ubuntu#install-nginx) oder [Apache](https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/linux-apache?view=aspnetcore-7.0) bereitstellen, oder Sie können einen http-over-dns-Tunnel wie [Cloudflared](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/install-and-setup/tunnel-guide/) verwenden. Außerdem sollten Sie daran denken, die Weiterleitungsheader Ihres Proxys korrekt aufzulösen, um die Informationen Ihres Clients, wie IP-Adresse und Host, über [Weiterleitungs-Resolver](https://docs.sisk-framework.org/de/docs/advanced/forwarding-resolvers.md) zu erhalten. Der nächste Schritt nach der Erstellung Ihres Tunnels, der Firewall-Konfiguration und dem Laufen Ihrer Anwendung ist, einen Dienst für Ihre Anwendung zu erstellen. > [!NOTE] > Die direkte Verwendung von SSL-Zertifikaten in der Sisk-Anwendung auf nicht-Windows-Systemen ist nicht möglich. Dies ist ein Punkt der Implementierung von HttpListener, der das zentrale Modul für die HTTP-Warteschlangenverwaltung in Sisk ist, und diese Implementierung variiert von Betriebssystem zu Betriebssystem. Sie können SSL in Ihrer Sisk-Anwendung verwenden, wenn Sie [ein Zertifikat mit dem virtuellen Host mit IIS zuordnen](https://learn.microsoft.com/en-us/iis/manage/configuring-security/how-to-set-up-ssl-on-iis). Für andere Systeme wird die Verwendung eines Reverse-Proxys stark empfohlen. ## Erstellen eines Dienstes Das Erstellen eines Dienstes macht Ihre Anwendung immer verfügbar, auch nach dem Neustart Ihres Server-Instanz oder einem nicht-wiederherstellbaren Absturz. In diesem einfachen Tutorial werden wir den Inhalt des vorherigen Tutorials als Showcase verwenden, um Ihren Dienst immer aktiv zu halten. 1. Greifen Sie auf den Ordner zu, in dem sich die Dienstkonfigurationsdateien befinden: ```sh cd /etc/systemd/system ``` 2. Erstellen Sie Ihre `my-app.service`-Datei und fügen Sie den Inhalt hinzu: ```ini {title="my-app.service"} [Unit] Description= [Service] # Setzen Sie den Benutzer, der den Dienst starten wird User= # Der ExecStart-Pfad ist nicht relativ zum WorkingDirectory. # Setzen Sie ihn als vollständigen Pfad zur ausführbaren Datei WorkingDirectory=/home/htdocs ExecStart=/home/htdocs/my-app # Setzen Sie den Dienst auf immer neu starten bei einem Absturz Restart=always RestartSec=3 [Install] WantedBy=multi-user.target ``` 3. Starten Sie Ihren Dienst-Manager-Modul neu: ```sh $ sudo systemctl daemon-reload ``` 4. Starten Sie Ihren neu erstellten Dienst vom Namen der Datei, die Sie festgelegt haben, und prüfen Sie, ob er läuft: ```sh $ sudo systemctl start my-app $ sudo systemctl status my-app ``` 5. Jetzt, wenn Ihre App läuft ("Active: active"), aktivieren Sie Ihren Dienst, um ihn nach einem System-Neustart weiterlaufen zu lassen: ```sh $ sudo systemctl enable my-app ``` Jetzt sind Sie bereit, loszulegen und Ihre Sisk-Anwendung allen zu präsentieren. --- # Arbeiten mit SSL Source: https://docs.sisk-framework.org/de/docs/ssl.html Die Arbeit mit SSL für die Entwicklung kann notwendig sein, wenn man in Kontexten arbeitet, die Sicherheit erfordern, wie die meisten Web‑Entwicklungsszenarien. Sisk läuft auf HttpListener, das kein natives HTTPS, sondern nur HTTP unterstützt. Es gibt jedoch Umgehungen, die es ermöglichen, SSL in Sisk zu verwenden. Siehe unten: ## Über die Sisk.Cadente.CoreEngine - Verfügbar auf: Linux, macOS, Windows - Aufwand: einfach Es ist möglich, die experimentelle [**Cadente**](https://docs.sisk-framework.org/de/docs/cadente.md)‑Engine in Sisk‑Projekten zu nutzen, ohne zusätzliche Konfiguration am Computer oder im Projekt vorzunehmen. Sie müssen das Paket `Sisk.Cadente.CoreEngine` in Ihrem Projekt installieren, um den Cadente‑Server im Sisk‑Server verwenden zu können. Um SSL zu konfigurieren, können Sie die Methoden `UseSsl` und `UseEngine` des Builders verwenden: ```csharp using var http = HttpServer.CreateBuilder() .UseEngine() .UseSsl(CertificateHelper.CreateTrustedDevelopmentCertificate("localhost")) ``` > Hinweis: Dieses Paket befindet sich noch in der experimentellen Phase. ## Über IIS unter Windows - Verfügbar auf: Windows - Aufwand: mittel Wenn Sie Windows verwenden, können Sie IIS einsetzen, um SSL auf Ihrem HTTP‑Server zu aktivieren. Damit dies funktioniert, sollten Sie vorher dem [Tutorial](https://docs.sisk-framework.org/de/docs/registering-namespace.md) folgen, falls Ihre Anwendung auf einem anderen Host als „localhost“ lauschen soll. Damit das funktioniert, müssen Sie IIS über die Windows‑Features installieren. IIS ist für Windows‑ und Windows‑Server‑Benutzer kostenlos verfügbar. Um SSL in Ihrer Anwendung zu konfigurieren, halten Sie das SSL‑Zertifikat bereit, selbst wenn es selbstsigniert ist. Anschließend können Sie nachlesen, [wie man SSL auf IIS 7 oder höher einrichtet](https://learn.microsoft.com/en-us/iis/manage/configuring-security/how-to-set-up-ssl-on-iis). ## Über mitmproxy - Verfügbar auf: Linux, macOS, Windows - Aufwand: einfach **mitmproxy** ist ein Interception‑Proxy‑Tool, das Entwicklern und Sicherheitstestern ermöglicht, HTTP‑ und HTTPS‑Verkehr zwischen einem Client (z. B. einem Webbrowser) und einem Server zu inspizieren, zu verändern und aufzuzeichnen. Sie können das Dienstprogramm **mitmdump** verwenden, um einen Reverse‑SSL‑Proxy zwischen Ihrem Client und Ihrer Sisk‑Anwendung zu starten. 1. Installieren Sie zunächst [mitmproxy](https://mitmproxy.org/) auf Ihrem Rechner. 2. Starten Sie Ihre Sisk‑Anwendung. In diesem Beispiel verwenden wir Port 8000 als unsicheren HTTP‑Port. 3. Starten Sie den mitmproxy‑Server, der auf dem sicheren Port 8001 lauscht: ```sh mitmdump --mode reverse:http://localhost:8000/ -p 8001 ``` Und Sie können loslegen! Sie können Ihre Anwendung bereits über `https://localhost:8001/` erreichen. Ihre Anwendung muss nicht laufen, damit Sie `mitmdump` starten können. Alternativ können Sie in Ihrem Projekt einen Verweis auf den [mitmproxy‑Helper](https://github.com/sisk-http/core/tree/main/extensions/Sisk.Helpers.mitmproxy) hinzufügen. Dies erfordert weiterhin, dass mitmproxy auf Ihrem Computer installiert ist. ## Über das Sisk.SslProxy‑Paket - Verfügbar auf: Linux, macOS, Windows - Aufwand: einfach > [!IMPORTANT] > > Das Sisk.SslProxy‑Paket ist zugunsten des `Sisk.Cadente.CoreEngine`‑Pakets veraltet und wird nicht mehr gepflegt. Das Sisk.SslProxy‑Paket ist ein einfacher Weg, SSL in Ihrer Sisk‑Anwendung zu aktivieren. Es handelt sich jedoch um ein **extrem experimentelles** Paket. Die Arbeit damit kann instabil sein, aber Sie können zu dem kleinen Prozentsatz der Personen gehören, die dazu beitragen, dieses Paket brauchbar und stabil zu machen. Um zu beginnen, können Sie das Sisk.SslProxy‑Paket installieren mit: ```sh dotnet add package Sisk.SslProxy ``` > [!NOTE] > > Sie müssen in Visual Studio den „Include prerelease“‑Schalter im NuGet‑Paket‑Manager aktivieren, um Sisk.SslProxy zu installieren. Noch einmal: Es ist ein experimentelles Projekt, also denken Sie nicht einmal daran, es in die Produktion zu übernehmen. Derzeit kann Sisk.SslProxy die meisten HTTP/1.1‑Funktionen handhaben, einschließlich HTTP Continue, Chunked‑Encoding, WebSockets und SSE. Lesen Sie mehr über SslProxy [hier](https://docs.sisk-framework.org/de/docs/extensions/ssl-proxy.md). --- # Cadente Source: https://docs.sisk-framework.org/de/docs/cadente.html Cadente ist eine experimentelle, verwaltete HTTP/1.1-Listener-Implementierung für Sisk. Sie dient als Ersatz für den Standard-`System.Net.HttpListener` und bietet eine größere Kontrolle und Flexibilität, insbesondere auf nicht-Windows-Plattformen. ## Überblick Standardmäßig verwendet Sisk `HttpListener` (aus `System.Net`) als seine zugrunde liegende HTTP-Server-Engine. Während `HttpListener` stabil und leistungsfähig auf Windows ist (wo er den kernel-modus-Treiber HTTP.sys verwendet), ist seine Implementierung auf Linux und macOS verwaltet und hatte historisch bedingt Einschränkungen, wie z. B. fehlende native SSL-Unterstützung (was einen Reverse-Proxy wie Nginx oder Sisk.SslProxy erfordert) und unterschiedliche Leistungsmerkmale. Cadente zielt darauf ab, diese Probleme zu lösen, indem es einen vollständig verwalteten HTTP/1.1-Server in C# bereitstellt. Seine Hauptziele sind: - **Native SSL-Unterstützung:** Funktioniert auf allen Plattformen ohne externe Proxys oder komplexe Konfiguration. - **Plattformübergreifende Konsistenz:** Identisches Verhalten auf Windows, Linux und macOS. - **Leistung:** Entwickelt als hochleistungsfähige Alternative zum verwalteten `HttpListener`. - **Unabhängigkeit:** Entkoppelt von `System.Net.HttpListener`, um Sisk vor möglichen zukünftigen Veraltungen oder mangelnder Wartung dieses Komponenten in .NET zu schützen. > [!WARNING] > **Experimenteller Status** > > Cadente befindet sich derzeit in einer experimentellen Phase (Beta). Es wird nicht empfohlen, es in kritischen Produktionsumgebungen zu verwenden. Die API und das Verhalten können sich ändern. ## Installation Cadente ist als separates Paket verfügbar. Um es mit Sisk zu verwenden, benötigen Sie das `Sisk.Cadente.CoreEngine`-Paket. ```bash dotnet add package Sisk.Cadente.CoreEngine --prerelease ``` ## Verwendung mit Sisk Um Cadente als HTTP-Motor für Ihre Sisk-Anwendung zu verwenden, müssen Sie den `HttpServer` so konfigurieren, dass er `CadenteHttpServerEngine` anstelle der Standard-Engine verwendet. Die `CadenteHttpServerEngine` passt den Cadente-`HttpHost` an die `HttpServerEngine`-Abstraktion an, die von Sisk erforderlich ist. ```csharp using Sisk.Core.Http; using Sisk.Cadente.CoreEngine; using var host = HttpServer.CreateBuilder() .UseEngine() .UseSsl(certificate: CertificateHelper.CreateTrustedDevelopmentCertificate("localhost")) .Build(); await host.StartAsync(); ``` ### Erweiterte Konfiguration Sie können die zugrunde liegende `HttpHost`-Instanz anpassen, indem Sie eine Setup-Aktion an den `CadenteHttpServerEngine`-Konstruktor übergeben. Dies ist nützlich für die Konfiguration von Timeouts oder anderen niedrigstufigen Einstellungen. ```csharp using var engine = new CadenteHttpServerEngine(host => { // Konfigurieren Sie die Client-Lese-/Schreibzeitüberschreitungen host.TimeoutManager.ClientReadTimeout = TimeSpan.FromSeconds(30); host.TimeoutManager.ClientWriteTimeout = TimeSpan.FromSeconds(30); }); ``` ## Verwendung als eigenständiger Server Obwohl Cadente in erster Linie für Sisk entwickelt wurde, kann es als eigenständiger HTTP-Server (ähnlich wie `HttpListener`) verwendet werden. ```csharp using Sisk.Cadente; var host = new HttpHost(15000) { Handler = new MyHostHandler() }; host.Start(); Thread.Sleep(-1); class MyHostHandler : HttpHostHandler { public override async Task OnContextCreatedAsync(HttpHost host, HttpHostContext context) { context.Response.StatusCode = 200; using var writer = new StreamWriter(context.Response.GetResponseStream()); await writer.WriteLineAsync("Hallo, Welt!"); } } ``` --- # Konfigurieren von Namensraumreservierungen unter Windows Source: https://docs.sisk-framework.org/de/docs/registering-namespace.html > [!NOTE] > Diese Konfiguration ist optional und nur erforderlich, wenn Sie möchten, dass Sisk unter Windows mit dem HttpListener‑Engine auf Hosts außer „localhost“ lauscht. Sisk arbeitet mit der HttpListener‑Netzwerkschnittstelle, die einen virtuellen Host an das System bindet, um auf Anfragen zu lauschen. Unter Windows ist diese Bindung etwas restriktiv und erlaubt nur localhost als gültigen Host. Beim Versuch, auf einen anderen Host zu lauschen, wird auf dem Server ein „Access denied“-Fehler ausgelöst. Dieses Tutorial erklärt, wie Sie die Berechtigung erteilen, auf jedem gewünschten Host des Systems zu lauschen. ```bat {title="Namespace Setup.bat"} @echo off :: Prefix hier einfügen, ohne Leerzeichen oder Anführungszeichen SET PREFIX= SET DOMAIN=%ComputerName%\%USERNAME% netsh http add urlacl url=%PREFIX% user=%DOMAIN% pause ``` Dabei ist `PREFIX` das Präfix („Listening Host->Port“), auf das Ihr Server lauschen soll. Es muss mit dem URL‑Schema, Host, Port und einem abschließenden Schrägstrich formatiert sein, Beispiel: ```bat {title="Namespace Setup.bat"} SET PREFIX=http://my-application.example.test/ ``` Damit Sie in Ihrer Anwendung darüber lauschen können: ```csharp {title="Program.cs"} class Program { static async Task Main(string[] args) { using var app = HttpServer.CreateBuilder() .UseListeningPort("http://my-application.example.test/") .Build(); app.Router.MapGet("/", request => { return new HttpResponse() { Status = 200, Content = new StringContent("Hello, world!") }; }); await app.StartAsync(); } } ``` --- # Changelogs Source: https://docs.sisk-framework.org/de/docs/changelogs.html Jeder Änderung, die an Sisk vorgenommen wird, wird über das Changelog protokolliert. Sie können die Changelogs für alle Sisk-Versionen [hier](https://github.com/sisk-http/archive/tree/master/changelogs) einsehen. --- # Häufig gestellte Fragen Source: https://docs.sisk-framework.org/de/docs/faq.html Häufig gestellte Fragen über Sisk. ## Ist Sisk Open-Source? Vollkommen. Alle Quellcode, die von Sisk verwendet werden, werden veröffentlicht und regelmäßig auf [GitHub](https://github.com/sisk-http) aktualisiert. ## Werden Beiträge akzeptiert? Solange sie mit der [Sisk-Philosophie](/) kompatibel sind, sind alle Beiträge sehr willkommen! Beiträge müssen nicht nur Code sein! Sie können mit Dokumentation, Tests, Übersetzungen, Spenden und Beiträgen helfen, zum Beispiel. ## Wird Sisk finanziell unterstützt? Nein. Keine Organisation oder Projekt unterstützt Sisk derzeit finanziell. ## Kann ich Sisk in der Produktion verwenden? Absolut. Das Projekt ist über drei Jahre in Entwicklung und wurde in kommerziellen Anwendungen intensiv getestet, die seitdem in Produktion sind. Sisk wird in wichtigen kommerziellen Projekten als Hauptinfrastruktur verwendet. Ein Leitfaden über die [Bereitstellung](https://docs.sisk-framework.org/de/docs/deploying.md) in verschiedenen Systemen und Umgebungen wurde geschrieben und ist verfügbar. ## Hat Sisk Authentifizierung, Überwachung und Datenbankdienste? Nein. Sisk hat keine davon. Es ist ein Framework für die Entwicklung von HTTP-Webanwendungen, aber es ist immer noch ein minimales Framework, das nur das Nötigste für die Funktionsweise Ihrer Anwendung liefert. Sie können alle Dienste, die Sie benötigen, mit jeder beliebigen Bibliothek implementieren, die Sie bevorzugen. Sisk wurde so konzipiert, dass es agnostisch, flexibel und mit allem kompatibel ist. ## Warum sollte ich Sisk anstelle von verwenden? Ich weiß nicht. Sie sagen es mir. Sisk wurde erstellt, um ein generisches Szenario für HTTP-Webanwendungen in .NET zu erfüllen. Etablierte Projekte wie ASP.NET lösen verschiedene Probleme, aber mit unterschiedlichen Vorurteilen. Im Gegensatz zu größeren Frameworks erfordert Sisk, dass der Benutzer weiß, was er tut und baut. Grundlegende Kenntnisse der Webentwicklung und des HTTP-Protokolls sind für die Arbeit mit Sisk unerlässlich. Sisk ist ASP.NET Core näher als das Express von Node.js. Es ist eine hohe Abstraktion, die es Ihnen ermöglicht, Anwendungen mit der HTTP-Logik zu erstellen, die Sie wollen. ## Was muss ich lernen, um Sisk zu verwenden? Sie benötigen die Grundlagen von: - Webentwicklung (HTTP, Restful usw.) - .NET Das ist alles. Wenn Sie eine Vorstellung von diesen beiden Themen haben, können Sie sich einige Stunden widmen, um eine fortschrittliche Anwendung mit Sisk zu entwickeln. ## Kann ich kommerzielle Anwendungen mit Sisk entwickeln? Absolut. Sisk wurde unter der MIT-Lizenz erstellt, was bedeutet, dass Sie Sisk in jedem kommerziellen Projekt verwenden können, kommerziell oder nicht-kommerziell, ohne dass eine proprietäre Lizenz erforderlich ist. Was wir bitten, ist, dass Sie irgendwo in Ihrer Anwendung einen Hinweis auf die verwendeten Open-Source-Projekte haben und dass Sisk dabei ist. --- # Routing Source: https://docs.sisk-framework.org/de/docs/fundamentals/routing.html Der [Router](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.md) ist der erste Schritt beim Aufbau des Servers. Er ist dafür verantwortlich, [Route](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Route.md)-Objekte zu verwalten, die Endpunkte darstellen, welche URLs und deren Methoden auf Aktionen abbilden, die vom Server ausgeführt werden. Jede Aktion ist dafür zuständig, eine Anfrage zu empfangen und eine Antwort an den Client zu liefern. Die Routen bestehen aus Paaren von Pfadausdrücken („Pfadmuster“) und der HTTP‑Methode, auf die sie hören können. Wenn eine Anfrage an den Server gestellt wird, versucht er, eine Route zu finden, die zur empfangenen Anfrage passt, ruft dann die Aktion dieser Route auf und liefert die resultierende Antwort an den Client. Es gibt mehrere Möglichkeiten, Routen in Sisk zu definieren: Sie können statisch, dynamisch oder automatisch gescannt sein, über Attribute definiert werden oder direkt im Router‑Objekt. ```cs Router mainRouter = new Router(); // mappt die GET / Route in die folgende Aktion mainRouter.MapGet("/", request => { return new HttpResponse("Hello, world!"); }); ``` Um zu verstehen, was eine Route leisten kann, müssen wir verstehen, was eine Anfrage leisten kann. Ein [HttpRequest](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.md) enthält alles, was Sie benötigen. Sisk enthält außerdem einige zusätzliche Features, die die Gesamtentwicklung beschleunigen. Für jede vom Server empfangene Aktion wird ein Delegat vom Typ [RouteAction](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouteAction.md) aufgerufen. Dieser Delegat enthält einen Parameter, der ein [HttpRequest](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.md) mit allen notwendigen Informationen über die vom Server empfangene Anfrage hält. Das Ergebnis dieses Delegaten muss ein [HttpResponse](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpResponse.md) sein oder ein Objekt, das über [implizite Antworttypen](https://docs.sisk-framework.org/de/docs/fundamentals/responses.md#implicit-response-types) darauf abgebildet wird. ## Matching routes Wenn eine Anfrage vom HTTP‑Server empfangen wird, sucht Sisk nach einer Route, die den Ausdruck des von der Anfrage empfangenen Pfads erfüllt. Der Ausdruck wird immer zwischen der Route und dem Anfrage‑Pfad getestet, ohne die Query‑String zu berücksichtigen. Dieser Test hat keine Priorität und ist exklusiv für eine einzelne Route. Wenn keine Route zu dieser Anfrage passt, wird die Antwort von [Router.NotFoundErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.NotFoundErrorHandler.md) an den Client zurückgegeben. Wenn das Pfadmuster passt, die HTTP‑Methode jedoch nicht, wird die Antwort von [Router.MethodNotAllowedErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.MethodNotAllowedErrorHandler.md) an den Client gesendet. Sisk prüft die Möglichkeit von Routenkollisionen, um diese Probleme zu vermeiden. Beim Definieren von Routen sucht Sisk nach möglichen Routen, die mit der zu definierenden Route kollidieren könnten. Dieser Test beinhaltet die Prüfung des Pfads und der Methode, die die Route akzeptieren soll. ### Creating routes using path patterns Für neue Anwendungen sollten die `Map*`‑Methoden bevorzugt werden. Sie halten die HTTP‑Methode am Aufrufort sichtbar und entsprechen der aktuellen `Router`‑API. Die älteren `SetRoute`‑Methoden existieren noch als Kompatibilitäts‑Wrapper, aber neue Beispiele sollten `Map`, `MapGet`, `MapPost`, `MapPut`, `MapDelete`, `MapPatch`, `MapAny`, `MapOptions` oder `MapHead` verwenden. ```cs // Map*-Methoden sind der übliche Weg, um methodenspezifische Routen zu definieren. mainRouter.MapGet("/hey/", (request) => { string name = request.RouteParameters["name"].GetString(); return new HttpResponse($"Hello, {name}"); }); mainRouter.MapPost("/form", (request) => { var formData = request.GetFormContent(); return new HttpResponse(); // leerer 200 OK }); // Map kann auch eine Route-Instanz erhalten, wenn Sie Routenoptionen benötigen. mainRouter.Map(Route.Get("/image.png", (request) => { var imageStream = File.OpenRead("image.png"); return new HttpResponse() { // das innere StreamContent // Stream wird nach dem Senden freigegeben // die Antwort. Content = new StreamContent(imageStream) }; })); // mehrere Parameter mainRouter.MapGet("/hey//surname/", (request) => { string name = request.RouteParameters["name"].GetString(); string surname = request.RouteParameters["surname"].GetString(); return new HttpResponse($"Hello, {name} {surname}!"); }); ``` Die [RouteParameters](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.RouteParameters.md)-Eigenschaft von HttpRequest enthält alle Informationen über die Pfadvariablen der empfangenen Anfrage. Jeder vom Server empfangene Pfad wird normalisiert, bevor der Pfadmuster‑Test ausgeführt wird, nach folgenden Regeln: - Alle leeren Segmente werden aus dem Pfad entfernt, z. B.: `////foo//bar` wird zu `/foo/bar`. - Pfad‑Matching ist **case‑sensitive**, es sei denn, [Router.MatchRoutesIgnoreCase](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.MatchRoutesIgnoreCase.md) ist auf `true` gesetzt. Die [Query](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Query.md)- und [RouteParameters](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.RouteParameters.md)-Eigenschaften von [HttpRequest](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.md) geben ein [StringValueCollection](https://docs.sisk-framework.org/api/Sisk.Core.Entity.StringValueCollection.md)-Objekt zurück, wobei jede indizierte Eigenschaft ein nicht‑null [StringValue](https://docs.sisk-framework.org/api/Sisk.Core.Entity.StringValue.md) liefert, das als Option/Monade verwendet werden kann, um seinen Rohwert in ein verwaltetes Objekt zu konvertieren. Das folgende Beispiel liest den Routen‑Parameter „id“ und erzeugt daraus ein `Guid`. Ist der Parameter kein gültiges Guid, wird eine Ausnahme geworfen und bei nicht aktivem [Router.CallbackErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.CallbackErrorHandler.md) ein 500‑Fehler an den Client zurückgegeben. ```cs mainRouter.MapGet("/user/", (request) => { Guid id = request.RouteParameters["id"].GetGuid(); return new HttpResponse($"User id: {id}"); }); ``` > [!NOTE] > Pfade ignorieren ihr abschließendes `/` sowohl in Anfrage‑ als auch in Routenkontext, d. h. wenn Sie versuchen, auf eine Route `/index/page` zuzugreifen, können Sie sie auch über `/index/page/` erreichen. > > Sie können URLs außerdem dazu zwingen, mit `/` zu enden, indem Sie [HttpServerConfiguration.ForceTrailingSlash](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ForceTrailingSlash.md) aktivieren. ### Creating routes using class instances Sie können Routen auch dynamisch über Reflection mit dem Attribut [RouteAttribute](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouteAttribute.md) definieren. Auf diese Weise werden die Routen einer Klasseninstanz, deren Methoden dieses Attribut implementieren, im Ziel‑Router definiert. Damit eine Methode als Route definiert werden kann, muss sie mit einem [RouteAttribute](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouteAttribute.md) markiert sein, etwa dem Attribut selbst oder einem [RouteGetAttribute](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouteGetAttribute.md). Die Methode kann static, instanziiert, public oder private sein. Verwenden Sie `MapInstance`, wenn Sie Instanz‑ und statische Routinemethoden aus einem Objekt zuordnen wollen. Verwenden Sie `MapType`, wenn Sie nur statische Routinemethoden aus einem Typ zuordnen wollen. ```cs {title="Controller/MyController.cs"} public class MyController { // passt zu GET / [RouteGet] HttpResponse Index(HttpRequest request) { HttpResponse res = new HttpResponse(); res.Content = new StringContent("Index!"); return res; } // statische Methoden funktionieren ebenfalls [RouteGet("/hello")] static HttpResponse Hello(HttpRequest request) { HttpResponse res = new HttpResponse(); res.Content = new StringContent("Hello world!"); return res; } } ``` Die Zeile unten definiert sowohl die `Index`‑ als auch die `Hello`‑Methoden von `MyController` als Routen, da beide als Routen markiert sind und eine Instanz der Klasse bereitgestellt wurde, nicht ihr Typ. Wäre stattdessen der Typ übergeben worden, würden nur die statischen Methoden definiert werden. ```cs var myController = new MyController(); mainRouter.MapInstance(myController); ``` Um nur statische Routinemethoden eines Typs zuzuordnen, verwenden Sie: ```cs mainRouter.MapType(); ``` Seit Sisk Version 0.16 ist es möglich, AutoScan zu aktivieren, wodurch nach benutzerdefinierten Klassen gesucht wird, die `RouterModule` implementieren, und diese automatisch dem Router zugeordnet werden. Dies wird bei AOT‑Kompilierung nicht unterstützt. ```cs mainRouter.AutoScanModules(); ``` Der obige Befehl sucht nach allen Typen, die `ApiController` implementieren, **nicht jedoch nach dem Typ selbst**. Die beiden optionalen Parameter geben an, wie die Methode nach diesen Typen sucht. Das erste Argument bezeichnet das Assembly, in dem die Typen gesucht werden, das zweite gibt an, wie die Typen definiert werden sollen. ## Regex routes Anstatt die standardmäßigen HTTP‑Pfad‑Matching‑Methoden zu verwenden, können Sie eine Route markieren, die mit Regex interpretiert wird. ```cs Route indexRoute = new RegexRoute(RouteMethod.Get, @"\/[a-z]+\/", IndexPage); mainRouter.Map(indexRoute); ``` Oder mit der [RegexRoute](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RegexRoute.md)-Klasse: ```cs mainRouter.Map(new RegexRoute(RouteMethod.Get, @"\/[a-z]+\/", request => { return new HttpResponse("hello, world"); })); ``` Sie können außerdem Gruppen aus dem Regex‑Muster in den Inhalt von [HttpRequest.RouteParameters](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.RouteParameters.md) übernehmen: ```cs {title="Controller/MyController.cs"} public class MyController { [RegexRoute(RouteMethod.Get, @"/uploads/(?.*\.(jpeg|jpg|png))")] static HttpResponse RegexRoute(HttpRequest request) { string filename = request.RouteParameters["filename"].GetString(); return new HttpResponse().WithContent($"Acessing file {filename}"); } } ``` ## Prefixing routes Sie können allen Routen einer Klasse oder eines Moduls das Attribut [RoutePrefix](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RoutePrefixAttribute.md) hinzufügen und das Präfix als Zeichenkette festlegen. Siehe das folgende Beispiel, das die BREAD‑Architektur (Browse, Read, Edit, Add und Delete) verwendet: ```cs {title="Controller/Api/UsersController.cs"} [RoutePrefix("/api/users")] public class UsersController { // GET /api/users [RouteGet] public async Task Browse() { ... } // GET /api/users/ [RouteGet("/")] public async Task Read() { ... } // PATCH /api/users/ [RoutePatch("/")] public async Task Edit() { ... } // POST /api/users [RoutePost] public async Task Add() { ... } // DELETE /api/users/ [RouteDelete("/")] public async Task Delete() { ... } } ``` Im obigen Beispiel wird der HttpResponse‑Parameter weggelassen, zugunsten der Nutzung über den globalen Kontext [HttpContext.Current](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpContext.Current.md). Weitere Details im folgenden Abschnitt. ## Routes without request parameter Routen können ohne den [HttpRequest](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.md)-Parameter definiert werden und dennoch im Anforderungskontext auf die Anfrage und ihre Komponenten zugreifen. Betrachten wir eine Abstraktion `ControllerBase`, die als Grundlage für alle Controller einer API dient und die `Request`‑Eigenschaft bereitstellt, um den aktuell gültigen [HttpRequest](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.md) zu erhalten. ```cs {title="Controller/ControllerBase.cs"} public abstract class ControllerBase { // holt die Anfrage aus dem aktuellen Thread public HttpRequest Request { get => HttpContext.Current.Request; } // die nachfolgende Zeile ruft, wenn sie aufgerufen wird, die Datenbank aus der aktuellen HTTP-Session ab, // oder erstellt eine neue, falls sie nicht existiert public DbContext Database { get => HttpContext.Current.RequestBag.GetOrAdd(); } } ``` Und damit alle Nachfolger die Routensyntax ohne Anfrage‑Parameter nutzen können: ```cs {title="Controller/UsersController.cs"} [RoutePrefix("/api/users")] public class UsersController : ControllerBase { [RoutePost] public async Task Create() { // liest die JSON-Daten aus der aktuellen Anfrage UserCreationDto? user = await Request.GetJsonContentAsync(); ... Database.Users.Add(user); return new HttpResponse(201); } } ``` Weitere Details zum aktuellen Kontext und zur Dependency Injection finden Sie im Tutorial zu [dependency injection](https://docs.sisk-framework.org/de/docs/features/instancing.md). ## Any method routes Sie können eine Route definieren, die nur anhand ihres Pfads und nicht anhand der HTTP‑Methode übereinstimmt. Das kann nützlich sein, um die Methodenvalidierung innerhalb des Route‑Callbacks durchzuführen. ```cs // passt zu / bei jeder HTTP-Methode mainRouter.MapAny("/", callbackFunction); ``` ## Any path routes Any‑Path‑Routen testen jeden vom HTTP‑Server empfangenen Pfad, wobei die zu testende Routinemethode berücksichtigt wird. Ist die Routinemethode `RouteMethod.Any` und verwendet die Route [Route.AnyPath](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Route.AnyPath.md) im Pfadausdruck, hört diese Route auf alle Anfragen des HTTP‑Servers, und es können keine weiteren Routen definiert werden. ```cs // die folgende Route passt zu allen POST-Anfragen mainRouter.Map(RouteMethod.Post, Route.AnyPath, callbackFunction); ``` ## Ignore case route matching Standardmäßig ist das Matching von Routen und Anfragen case‑sensitive. Um die Groß‑/Kleinschreibung zu ignorieren, aktivieren Sie diese Option: ```cs mainRouter.MatchRoutesIgnoreCase = true; ``` Damit wird ebenfalls die Option `RegexOptions.IgnoreCase` für Routen aktiviert, bei denen Regex‑Matching verwendet wird. ## Not Found (404) callback handler Sie können einen benutzerdefinierten Callback erstellen, der ausgeführt wird, wenn eine Anfrage zu keiner bekannten Route passt. ```cs mainRouter.NotFoundErrorHandler = () => { return new HttpResponse(404) { // Seit v0.14 Content = new HtmlContent("

Not found

") // ältere Versionen Content = new StringContent("

Not found

", Encoding.UTF8, "text/html") }; }; ``` ## Method not allowed (405) callback handler Sie können ebenfalls einen benutzerdefinierten Callback erstellen, der ausgeführt wird, wenn eine Anfrage zwar zum Pfad, aber nicht zur Methode passt. ```cs mainRouter.MethodNotAllowedErrorHandler = (context) => { return new HttpResponse(405) { Content = new StringContent($"Method not allowed for this route.") }; }; ``` ## Error Handling Ausnahmen können innerhalb des Anfrage‑Lebenszyklus geworfen werden, der vom Pre‑Execution‑Request‑Handler über die Router‑Aktion bis zu den Post‑Execution‑Request‑Handlern und Value‑Handlern reicht. Diese Ausnahmen werden durch den folgenden Mechanismus verwaltet: - Wenn [HttpServerConfiguration.ThrowExceptions](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ThrowExceptions.md) `true` ist, werden Ausnahmen normal geworfen und nicht von Sisk abgefangen; der HTTP‑Server kann bei nicht abgefangener Ausnahme unterbrochen werden. - Wenn [HttpServerConfiguration.ThrowExceptions](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ThrowExceptions.md) `false` ist, werden Ausnahmen von Sisk abgefangen und verarbeitet. Danach, falls `Router.CallbackErrorHandler` definiert ist, wird er mit der abgefangenen Ausnahme und dem Anfrage‑Kontext aufgerufen und **nicht** an die Standard‑Fehlerausgabe weitergeleitet. Ist `Router.CallbackErrorHandler` nicht definiert, wird die Ausnahme an die Standard‑Fehlerausgabe weitergeleitet und der Client erhält eine HTTP‑500‑Fehlerantwort. Ist die Standard‑Fehlerausgabe nicht definiert, wird der Fehler stillschweigend ignoriert. Hinweis: Innerhalb von `Router.CallbackErrorHandler` können Sie den Log‑Modus für Fehler, Zugriffs‑Log, beides oder keins festlegen und das Standard‑Log‑Schreibverhalten ändern: ```csharp router.CallbackErrorHandler = (ex, ctx) => { ctx.LogMode = LogOutput.Both; // überschreibt den Logmodus, um den Fehler sowohl im Zugriffs- als auch im Fehlerprotokoll zu protokollieren } ``` ## Internal error handler Route‑Callbacks können während der Serverausführung Fehler werfen. Wird dies nicht korrekt behandelt, kann die Gesamtfunktion des HTTP‑Servers beendet werden. Der Router verfügt über einen Callback für den Fall, dass ein Route‑Callback fehlschlägt und einen Service‑Abbruch verhindert. Diese Methode ist nur erreichbar, wenn [ThrowExceptions](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ThrowExceptions.md) auf `false` gesetzt ist. ```cs mainRouter.CallbackErrorHandler = (ex, context) => { return new HttpResponse(500) { Content = new StringContent($"Error: {ex.Message}") }; }; ``` --- # Anfrageverarbeitung Source: https://docs.sisk-framework.org/de/docs/fundamentals/request-handlers.html Request-Handler, auch als „Middlewares“ bezeichnet, sind Funktionen, die vor oder nach der Ausführung einer Anfrage im Router laufen. Sie können pro Route oder pro Router definiert werden. Es gibt zwei Arten von Request-Handlern: - **BeforeResponse**: definiert, dass der Request-Handler vor dem Aufruf der Router-Aktion ausgeführt wird. - **AfterResponse**: definiert, dass der Request-Handler nach dem Aufruf der Router-Aktion ausgeführt wird. Das Senden einer HTTP-Antwort in diesem Kontext überschreibt die Antwort der Router-Aktion. Beide Request-Handler können die eigentliche Rückgabe der Router-Callback-Funktion überschreiben. Übrigens können Request-Handler nützlich sein, um eine Anfrage zu validieren, z. B. Authentifizierung, Inhalt oder andere Informationen, wie das Speichern von Daten, Protokolle oder weitere Schritte, die vor oder nach einer Antwort durchgeführt werden können. ![](https://docs.sisk-framework.org/assets/img/requesthandlers1.png) Auf diese Weise kann ein Request-Handler die gesamte Ausführung unterbrechen und eine Antwort zurückgeben, bevor der Zyklus abgeschlossen ist, wobei alles andere im Prozess verworfen wird. Beispiel: Angenommen, ein Request-Handler zur Benutzer-Authentifizierung authentifiziert den Benutzer nicht. Er verhindert, dass der Anfrage-Lebenszyklus fortgesetzt wird, und lässt ihn hängen. Wenn dies im Request-Handler an Position zwei geschieht, werden der dritte und alle folgenden nicht mehr ausgewertet. ![](https://docs.sisk-framework.org/assets/img/requesthandlers2.png) ## Erstellen eines Request-Handlers Um einen Request-Handler zu erstellen, können wir eine Klasse erzeugen, die das [IRequestHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.IRequestHandler.md)-Interface erbt, in folgendem Format: ```cs {title="Middleware/AuthenticateUserRequestHandler.cs"} public class AuthenticateUserRequestHandler : IRequestHandler { public RequestHandlerExecutionMode ExecutionMode { get; init; } = RequestHandlerExecutionMode.BeforeResponse; public HttpResponse? Execute(HttpRequest request, HttpContext context) { if (request.Headers.Authorization != null) { // Rückgabe von null bedeutet, dass der Anfragezyklus fortgesetzt werden kann return null; } else { // Rückgabe eines HttpResponse-Objekts bedeutet, dass diese Antwort benachbarte Antworten überschreibt. return new HttpResponse(System.Net.HttpStatusCode.Unauthorized); } } } ``` Im obigen Beispiel haben wir angegeben, dass wenn der `Authorization`-Header in der Anfrage vorhanden ist, die Verarbeitung fortgesetzt werden soll und der nächste Request-Handler oder der Router-Callback aufgerufen wird, je nachdem, was als Nächstes kommt. Wird ein Request-Handler nach der Antwort ausgeführt, indem seine Eigenschaft [ExecutionMode](https://docs.sisk-framework.org/api/Sisk.Core.Routing.IRequestHandler.ExecutionMode.md) auf AfterResponse gesetzt ist und ein Nicht-Null-Wert zurückgegeben wird, überschreibt er die Antwort des Routers. Immer wenn ein Request-Handler `null` zurückgibt, bedeutet das, dass die Anfrage fortgesetzt werden muss und das nächste Objekt aufgerufen wird bzw. der Zyklus mit der Antwort des Routers endet. Wenn Sie von der integrierten Klasse [RequestHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RequestHandler.md) erben, können Sie `Next()` zurückgeben, um diese Absicht explizit zu machen: ```cs public class AuthenticateUserRequestHandler : RequestHandler { public override HttpResponse? Execute(HttpRequest request, HttpContext context) { if (request.Headers.Authorization is not null) return Next(); return new HttpResponse(System.Net.HttpStatusCode.Unauthorized); } } ``` Für Handler, die I/O benötigen, erben Sie von [AsyncRequestHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.AsyncRequestHandler.md): ```cs public class LoadUserRequestHandler : AsyncRequestHandler { public override async Task ExecuteAsync(HttpRequest request, HttpContext context) { var user = await UserRepository.FindAsync(request.Headers.Authorization, request.DisconnectToken); if (user is null) return new HttpResponse(System.Net.HttpStatusCode.Unauthorized); request.Bag.Set(user); return Next(); } } ``` Kleine Inline-Handler können ebenfalls mit `RequestHandler.Create` oder `AsyncRequestHandler.Create` erstellt werden: ```cs var requireJson = RequestHandler.Create((request, context) => { if (request.Headers.ContentType?.Contains("application/json") == true) return null; return new HttpResponse(System.Net.HttpStatusCode.UnsupportedMediaType); }); ``` ## Zuordnen eines Request-Handlers zu einer einzelnen Route Sie können einen oder mehrere Request-Handler für eine Route definieren. ```cs {title="Router.cs"} mainRouter.Map(RouteMethod.Get, "/", IndexPage, new IRequestHandler[] { new AuthenticateUserRequestHandler(), // before request handler new ValidateJsonContentRequestHandler(), // before request handler // -- method IndexPage will be executed here new WriteToLogRequestHandler() // after request handler }); ``` Oder ein [Route](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Route.md)-Objekt erstellen: ```cs {title="Router.cs"} Route indexRoute = Route.Get("/", IndexPage); indexRoute.RequestHandlers = new IRequestHandler[] { new AuthenticateUserRequestHandler() }; mainRouter.Map(indexRoute); ``` ## Zuordnen eines Request-Handlers zu einem Router Sie können einen globalen Request-Handler definieren, der auf allen Routen eines Routers ausgeführt wird. ```cs {title="Router.cs"} mainRouter.GlobalRequestHandlers = new IRequestHandler[] { new AuthenticateUserRequestHandler() }; ``` ## Zuordnen eines Request-Handlers zu einem Attribut Sie können einen Request-Handler als Method-Attribut zusammen mit einem Route-Attribut definieren. ```cs {title="Controller/MyController.cs"} public class MyController { [RouteGet("/")] [RequestHandler] static HttpResponse Index(HttpRequest request) { return new HttpResponse() { Content = new StringContent("Hello world!") }; } } ``` Beachten Sie, dass der gewünschte Request-Handler-Typ und nicht eine Objektinstanz übergeben werden muss. Auf diese Weise wird der Request-Handler vom Router-Parser instanziiert. Sie können Argumente im Klassenkonstruktor über die Eigenschaft [ConstructorArguments](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RequestHandlerAttribute.ConstructorArguments.md) übergeben. Beispiel: ```cs {title="Controller/MyController.cs"} [RequestHandler("arg1", 123, ...)] public HttpResponse Index(HttpRequest request) { return res = new HttpResponse() { Content = new StringContent("Hello world!") }; } ``` Sie können auch Ihr eigenes Attribut erstellen, das RequestHandler implementiert: ```cs {title="Middleware/Attributes/AuthenticateAttribute.cs"} public class AuthenticateAttribute : RequestHandlerAttribute { public AuthenticateAttribute() : base(typeof(AuthenticateUserRequestHandler), ConstructorArguments = new object?[] { "arg1", 123, ... }) { ; } } ``` Und verwenden Sie es wie folgt: ```cs {title="Controller/MyController.cs"} [Authenticate] static HttpResponse Index(HttpRequest request) { return res = new HttpResponse() { Content = new StringContent("Hello world!") }; } ``` ## Umgehen eines globalen Request-Handlers Nachdem Sie einen globalen Request-Handler für eine Route definiert haben, können Sie diesen Request-Handler für bestimmte Routen ignorieren. ```cs {title="Router.cs"} var myRequestHandler = new AuthenticateUserRequestHandler(); mainRouter.GlobalRequestHandlers = new IRequestHandler[] { myRequestHandler }; Route publicRoute = Route.Get("/", IndexPage); publicRoute.Name = "My route"; publicRoute.BypassGlobalRequestHandlers = new IRequestHandler[] { myRequestHandler, // ok: the same instance of what is in the global request handlers new AuthenticateUserRequestHandler() // wrong: will not skip the global request handler }; mainRouter.Map(publicRoute); ``` > [!NOTE] > Wenn Sie einen Request-Handler umgehen, müssen Sie dieselbe Referenz verwenden, die Sie zuvor instanziiert haben, um ihn zu überspringen. Das Erstellen einer anderen Request-Handler-Instanz wird den globalen Request-Handler nicht überspringen, da sich die Referenz ändert. Denken Sie daran, dieselbe Request-Handler-Referenz zu verwenden, die sowohl in GlobalRequestHandlers als auch in BypassGlobalRequestHandlers verwendet wird. --- # Anfragen Source: https://docs.sisk-framework.org/de/docs/fundamentals/requests.html Anfragen sind Strukturen, die eine HTTP-Anforderungsnachricht darstellen. Das [HttpRequest](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.md)-Objekt enthält nützliche Funktionen zum Umgang mit HTTP-Nachrichten in Ihrer Anwendung. Eine HTTP-Anfrage besteht aus Methode, Pfad, Version, Headern und Body. In diesem Dokument zeigen wir Ihnen, wie Sie jedes dieser Elemente erhalten. ## Abrufen der Anfragemethode Um die Methode der empfangenen Anfrage zu erhalten, können Sie die Property **Method** verwenden: ```cs static HttpResponse Index(HttpRequest request) { HttpMethod requestMethod = request.Method; ... } ``` Diese Eigenschaft gibt die Methode der Anfrage zurück, dargestellt durch ein [HttpMethod](https://learn.microsoft.com/pt-br/dotnet/api/system.net.http.httpmethod)-Objekt. > [!NOTE] > Im Gegensatz zu Routemethoden dient diese Eigenschaft nicht dem [RouteMethod.Any](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouteMethod.md)-Element. Stattdessen gibt sie die tatsächliche Anfragemethode zurück. ## Abrufen von URL-Komponenten der Anfrage Sie können verschiedene Komponenten einer URL über bestimmte Eigenschaften einer Anfrage erhalten. Für dieses Beispiel betrachten wir die URL: ``` http://localhost:5000/user/login?email=foo@bar.com ``` | Komponentenname | Beschreibung | Komponentenwert | | --- | --- | --- | | [Path](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Path.md) | Gibt den Anforderungspfad zurück. | `/user/login` | | [FullPath](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.FullPath.md) | Gibt den Anforderungspfad und die Abfragezeichenfolge zurück. | `/user/login?email=foo@bar.com` | | [FullUrl](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.FullUrl.md) | Gibt die gesamte URL-Anforderungszeichenfolge zurück. | `http://localhost:5000/user/login?email=foo@bar.com` | | [Host](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Host.md) | Gibt den Host der Anfrage zurück. | `localhost` | | [Authority](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Authority.md) | Gibt den Host und Port der Anfrage zurück. | `localhost:5000` | | [QueryString](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.QueryString.md) | Gibt die Abfrage der Anfrage zurück. | `?email=foo@bar.com` | | [Query](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Query.md) | Gibt die Abfrage der Anfrage in einer benannten Wertsammlung zurück. | `{StringValueCollection object}` | | [IsSecure](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.IsSecure.md) | Bestimmt, ob die Anfrage SSL verwendet (true) oder nicht (false). | `false` | Sie können auch die Property [HttpRequest.Uri](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Uri.md) verwenden, die alles oben Genannte in einem Objekt enthält. ## Anforderungs-Metadaten und Abbruch Sisk fügt jeder Anfrage außerdem betriebliche Metadaten hinzu. Diese Eigenschaften sind nützlich für Protokolle, Tracing, Lokalisierung, Diagnose und langlaufende Vorgänge: | Eigenschaft oder Methode | Verwendung | | --- | --- | | [RequestId](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.RequestId.md) | Ein eindeutiger Bezeichner für die Anfrage. Aktivieren Sie [IncludeRequestIdHeader](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.IncludeRequestIdHeader.md), um ihn als `X-Request-Id` zurückzugeben. | | [RequestedAt](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.RequestedAt.md) | Der Zeitpunkt, zu dem Sisk das Anforderungsobjekt erstellt hat. | | [RemoteAddress](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.RemoteAddress.md) | Die vom Verbindungsaufbau ermittelte Clientadresse oder aus Ihrem [ForwardingResolver](https://docs.sisk-framework.org/de/docs/advanced/forwarding-resolvers.md). | | [Culture](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Culture.md) | Die am besten aus `Accept-Language` ermittelte Kultur, mit Rückfall zur aktuellen Kultur. | | [DisconnectToken](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.DisconnectToken.md) | Ein Abbruch-Token, das ausgelöst wird, wenn der Client die Verbindung trennt, sofern vom konfigurierten HTTP-Engine unterstützt. | | [Bag](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Bag.md) | Ein typisierter Schlüssel/Wert‑Speicher, der über Anforderungs‑Handler und die Routinenaktion hinweg geteilt wird. | | [GetRawHttpRequest](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.GetRawHttpRequest.md) | Eine Textdarstellung der Anfrage für Diagnosezwecke. | ## Abrufen des Anfragetextes Einige Anfragen enthalten einen Body, z. B. Formulare, Dateien oder API‑Transaktionen. Sie können den Body einer Anfrage über die Property erhalten: ```cs // Holt den Anforderungstext als Zeichenkette, wobei die Anforderungs‑Codierung als Encoder verwendet wird string body = request.Body; // oder holt ihn als Byte‑Array byte[] bodyBytes = request.RawBody; // alternativ kann er gestreamt werden. Stream requestStream = request.GetRequestStream(); // oder den Body asynchron lesen Memory bodyMemory = await request.GetBodyContentsAsync(); ``` Es ist außerdem möglich zu bestimmen, ob ein Body vorhanden ist und ob er geladen wurde, über die Properties [HasContents](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.HasContents.md) (bestimmt, ob die Anfrage Inhalte hat) und [IsContentAvailable](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.IsContentAvailable.md) (zeigt an, dass der HTTP‑Server den Inhalt vollständig vom Remote‑Endpunkt erhalten hat). Es ist nicht möglich, den Anforderungsinhalt über `GetRequestStream` mehr als einmal zu lesen. Wenn Sie diese Methode verwenden, stehen die Werte in `RawBody` und `Body` ebenfalls nicht mehr zur Verfügung. Es ist nicht nötig, den Request‑Stream im Kontext der Anfrage zu disposen, da er am Ende der HTTP‑Sitzung, in der er erstellt wurde, automatisch freigegeben wird. Außerdem können Sie die Property [HttpRequest.RequestEncoding](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.RequestEncoding.md) nutzen, um die beste Codierung zum manuellen Dekodieren der Anfrage zu erhalten. Der Server hat Beschränkungen beim Lesen des Anforderungsinhalts, die sowohl für [HttpRequest.Body](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Body.md) als auch für [HttpRequest.RawBody](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Body.md) gelten. Diese Eigenschaften kopieren den gesamten Eingabestream in einen lokalen Puffer der Größe von [HttpRequest.ContentLength](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.ContentLength.md). Eine Antwort mit dem Status **413 Content Too Large** wird an den Client gesendet, wenn der gesendete Inhalt größer ist als [HttpServerConfiguration.MaximumContentLength](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.MaximumContentLength.md), das in der Benutzerkonfiguration definiert ist. Zusätzlich, wenn kein Limit konfiguriert ist oder es zu groß ist, wirft der Server eine [OutOfMemoryException](https://learn.microsoft.com/en-us/dotnet/api/system.outofmemoryexception?view=net-8.0), sobald der vom Client gesendete Inhalt [Int32.MaxValue](https://learn.microsoft.com/en-us/dotnet/api/system.int32.maxvalue) (2 GB) überschreitet und wenn versucht wird, über eine der oben genannten Properties darauf zuzugreifen. Sie können den Inhalt weiterhin über Streaming verarbeiten. > [!NOTE] > Obwohl Sisk dies erlaubt, ist es stets ratsam, den HTTP‑Semantiken zu folgen, um Ihre Anwendung zu erstellen und Inhalte nicht in Methoden zu erhalten oder zu liefern, die dies nicht zulassen. Lesen Sie mehr über [RFC 9110 „HTTP Semantics“](https://httpwg.org/spec/rfc9110.html). ## Lesen von JSON-Anfragen Für JSON‑APIs sollten Sie die integrierten JSON‑Hilfsfunktionen verwenden, anstatt `Body` zu lesen und manuell zu deserialisieren. Sie nutzen [System.Text.Json](https://learn.microsoft.com/en-us/dotnet/api/system.text.json) und greifen standardmäßig auf [HttpRequest.DefaultJsonSerializerOptions](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.DefaultJsonSerializerOptions.md) zurück. ```cs public record CreateUserRequest(string Name, string Email); router.MapPost("/users", (HttpRequest request) => { CreateUserRequest? body = request.GetJsonContent(); if (body is null) return new HttpResponse(System.Net.HttpStatusCode.BadRequest); return new HttpResponse(System.Net.HttpStatusCode.Created); }); ``` Verwenden Sie die asynchrone Überladung, wenn Sie bereits in einer asynchronen Route sind oder die Anfragenabbruch‑Funktion zum Stoppen der Deserialisierung nutzen möchten: ```cs router.MapPost("/users", async (HttpRequest request) => { CreateUserRequest? body = await request.GetJsonContentAsync(request.DisconnectToken); if (body is null) return new HttpResponse(System.Net.HttpStatusCode.BadRequest); return new HttpResponse(System.Net.HttpStatusCode.Created); }); ``` Sie können benutzerdefinierte [JsonSerializerOptions](https://learn.microsoft.com/en-us/dotnet/api/system.text.json.jsonserializeroptions) für einen bestimmten Endpunkt übergeben: ```cs var options = new JsonSerializerOptions(JsonSerializerDefaults.Web) { PropertyNameCaseInsensitive = true }; UserDto? user = request.GetJsonContent(options); ``` Für Native‑AOT‑ oder trim‑sensible Anwendungen verwenden Sie die `JsonTypeInfo`‑Überladung, die von einem `JsonSerializerContext` erzeugt wird: ```cs [JsonSerializable(typeof(CreateUserRequest))] public partial class AppJsonSerializerContext : JsonSerializerContext { } CreateUserRequest? body = await request.GetJsonContentAsync( AppJsonSerializerContext.Default.CreateUserRequest, request.DisconnectToken); ``` Die gleiche „einmal‑lesen“-Regel gilt für JSON‑Hilfen: Nachdem Sisk den Request‑Stream über `GetJsonContent`, `GetJsonContentAsync`, `Body` oder `RawBody` gelesen hat, können Sie den gleichen Body später nicht mehr über `GetRequestStream()` konsumieren. ## Abrufen des Anforderungskontexts Der HTTP‑Context ist ein exklusives Sisk‑Objekt, das Informationen über HTTP‑Server, Route, Router und Request‑Handler speichert. Sie können ihn nutzen, um sich in einer Umgebung zu organisieren, in der diese Objekte schwer zu handhaben sind. Sie können den aktuell ausgeführten [HttpContext](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpContext.md) über die statische Methode `HttpContext.GetCurrentContext()` erhalten. Diese Methode gibt den Kontext der gerade in dem aktuellen Thread verarbeiteten Anfrage zurück. ```cs HttpContext context = HttpContext.GetCurrentContext(); ``` ### Protokollmodus Die Property [HttpContext.LogMode](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpContext.LogMode.md) ermöglicht es Ihnen, das Logging‑Verhalten für die aktuelle Anfrage zu steuern. Sie können das Logging für bestimmte Anfragen aktivieren oder deaktivieren und damit die Standard‑Serverkonfiguration überschreiben. ```cs // Logging für diese Anfrage deaktivieren context.LogMode = LogOutputMode.None; ``` ### Anforderungsbeutel Das [RequestBag](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpContext.RequestBag.md)-Objekt enthält gespeicherte Informationen, die von einem Request‑Handler an einen anderen Punkt weitergegeben werden und am Zielort konsumiert werden können. Dieses Objekt kann auch von Request‑Handlern verwendet werden, die nach dem Routinen‑Callback ausgeführt werden. > [!TIP] > Diese Property ist ebenfalls über die Property [HttpRequest.Bag](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Bag.md) zugänglich. ```cs {title="Middleware/AuthenticateUserRequestHandler.cs"} public class AuthenticateUserRequestHandler : IRequestHandler { public string Identifier { get; init; } = Guid.NewGuid().ToString(); public RequestHandlerExecutionMode ExecutionMode { get; init; } = RequestHandlerExecutionMode.BeforeResponse; public HttpResponse? Execute(HttpRequest request, HttpContext context) { if (request.Headers.Authorization != null) { context.RequestBag.Add("AuthenticatedUser", new User("Bob")); return null; } else { return new HttpResponse(System.Net.HttpStatusCode.Unauthorized); } } } ``` Der obige Request‑Handler legt `AuthenticatedUser` im Request‑Bag ab und kann später im finalen Callback konsumiert werden: ```cs {title="Controller/MyController.cs"} public class MyController { [RouteGet("/")] [RequestHandler] static HttpResponse Index(HttpRequest request) { User authUser = request.Context.RequestBag["AuthenticatedUser"]; return new HttpResponse() { Content = new StringContent($"Hello, {authUser.Name}!") }; } } ``` Sie können außerdem die Hilfsmethoden `Bag.Set()` und `Bag.Get()` verwenden, um Objekte anhand ihrer Typ‑Singletons zu setzen bzw. zu holen. Die Klasse `TypedValueDictionary` stellt zudem die Methoden `GetValue` und `SetValue` für mehr Kontrolle bereit. ```cs {title="Middleware/Authenticate.cs"} public class Authenticate : RequestHandler { public override HttpResponse? Execute(HttpRequest request, HttpContext context) { request.Bag.Set(authUser); } } ``` ```csharp {title="Controller/MyController.cs"} [RouteGet("/")] [RequestHandler] public static HttpResponse GetUser(HttpRequest request) { var user = request.Bag.Get(); ... } ``` ## Abrufen von Formulardaten Sie können Formulardatenwerte in einer [StringKeyStoreCollection](https://docs.sisk-framework.org/api/Sisk.Core.Entity.StringKeyStoreCollection.md) mit dem untenstehenden Beispiel erhalten: ```cs {title="Controller/Auth.cs"} [RoutePost("/auth")] public HttpResponse Index(HttpRequest request) { var form = request.GetFormContent(); string? username = form["username"]; string? password = form["password"]; if (AttempLogin(username, password)) { ... } } ``` Die asynchrone Version ist nützlich, wenn der Request‑Body groß sein kann oder Sie eine Abbruch‑Unterstützung benötigen: ```cs var form = await request.GetFormContentAsync(request.DisconnectToken); ``` ## Abrufen von multipart-Formulardaten Sisk‑HTTP‑Requests ermöglichen das Abrufen hochgeladener multipart‑Inhalte, wie Dateien, Formulardaten oder beliebiger Binärdaten. ```cs {title="Controller/Auth.cs"} [RoutePost("/upload-contents")] public HttpResponse Index(HttpRequest request) { // Die folgende Methode liest die gesamte Anforderungs‑Eingabe in ein // Array von MultipartObjects ein var multipartFormDataObjects = request.GetMultipartFormContent(); foreach (MultipartObject uploadedObject in multipartFormDataObjects) { // Der Name der Datei, die durch Multipart‑Formulardaten bereitgestellt wird. // Null wird zurückgegeben, wenn das Objekt keine Datei ist. Console.WriteLine("File name : " + uploadedObject.Filename); // Der Feldname des Multipart‑Formulardatenobjekts. Console.WriteLine("Field name : " + uploadedObject.Name); // Die Inhaltslänge des Multipart‑Formulardatenobjekts. Console.WriteLine("Content length : " + uploadedObject.ContentLength); // Bestimmt das Bildformat basierend auf dem Dateikopf für jeden // bekannten Inhaltstyp. Wenn der Inhalt kein erkanntes gängiges Dateiformat ist, // gibt diese Methode MultipartObjectCommonFormat.Unknown zurück Console.WriteLine("Common format : " + uploadedObject.GetCommonFileFormat()); } } ``` Verwenden Sie [GetMultipartFormContentAsync](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.GetMultipartFormContentAsync.md), wenn die Route asynchron ist: ```cs var multipartFormDataObjects = await request.GetMultipartFormContentAsync(request.DisconnectToken); ``` Weitere Informationen zu Sisk‑[Multipart‑Form‑Objects](https://docs.sisk-framework.org/api/Sisk.Core.Entity.MultipartObject.md) sowie zu deren Methoden, Eigenschaften und Funktionalitäten finden Sie in der Dokumentation. ## Erkennen von Client-Disconnects Seit Version v1.15 von Sisk stellt das Framework über [HttpRequest.DisconnectToken](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.DisconnectToken.md) ein Abbruch‑Token bereit. Wenn die konfigurierte HTTP‑Engine die Erkennung von Disconnects unterstützt, wird dieses Token abgebrochen, sobald die Client‑Verbindung geschlossen wird, bevor die Antwort abgeschlossen ist. Das ist nützlich, um langlaufende Vorgänge zu stoppen, wenn der Client nicht mehr auf das Ergebnis wartet. ```csharp router.MapGet("/connect", async (HttpRequest req) => { // Holt das Disconnect‑Token aus der Anfrage var dc = req.DisconnectToken; await LongOperationAsync(dc); return new HttpResponse(); }); ``` Dieses Token ist nicht mit allen HTTP‑Engines kompatibel, und jede erfordert eine eigene Implementierung. Die Standard‑Sisk‑Engine, basierend auf `System.Net.HttpListener`, unterstützt keine Client‑Disconnect‑Erkennung. Wenn Ihre Anwendung die Standard‑Engine nutzt, ist `DisconnectToken` gleich `CancellationToken.None`; praktisch handelt es sich um ein nicht‑abbrechbares Token, das als nicht verfügbar behandelt werden sollte. Die [Cadente‑Engine](https://docs.sisk-framework.org/de/docs/cadente.md) unterstützt `DisconnectToken`. Wenn Ihre Route von einer disconnect‑bewussten Abbruch‑Logik abhängt, verwenden Sie Cadente oder eine andere Engine, die dieses Verhalten explizit implementiert. Selbst bei einer unterstützten Engine ist das Abbrechen kooperativ: Geben Sie das Token an asynchrone APIs weiter und prüfen Sie es in Ihrer eigenen langlaufenden Arbeit. ## Unterstützung von Server‑Sent Events Sisk unterstützt [Server‑Sent Events](https://developer.mozilla.org/en-US/docs/de/Web/API/Server-sent_events), wodurch Datenblöcke als Stream gesendet und die Verbindung zwischen Server und Client aufrecht erhalten werden kann. Der Aufruf der Methode [HttpRequest.GetEventSource](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.GetEventSource.md) versetzt das HttpRequest in einen Listener‑Zustand. Dadurch erwartet der Kontext dieser HTTP‑Anfrage keine HttpResponse, da sie die von Server‑Side‑Events gesendeten Pakete überlagern würde. Nach dem Senden aller Pakete muss der Callback die Methode [Close](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequestEventSource.Close.md) zurückgeben, die die finale Antwort an den Server sendet und das Ende des Streamings signalisiert. Es ist nicht möglich, die Gesamtlänge aller zu sendenden Pakete vorherzusagen, sodass das Ende der Verbindung nicht über den Header `Content‑Length` bestimmt werden kann. Nach den Standardeinstellungen der meisten Browser unterstützen Server‑Side‑Events das Senden von HTTP‑Headern oder anderen Methoden als GET nicht. Seien Sie daher vorsichtig, wenn Sie Request‑Handler mit Event‑Source‑Anfragen verwenden, die spezifische Header benötigen, da diese wahrscheinlich nicht vorhanden sind. Außerdem starten die meisten Browser Streams neu, wenn die Methode [EventSource.close](https://developer.mozilla.org/en-US/docs/de/Web/API/EventSource/close) auf der Client‑Seite nach dem Empfang aller Pakete nicht aufgerufen wird, was zu einer unendlichen zusätzlichen Verarbeitung auf der Server‑Seite führt. Um dieses Problem zu vermeiden, ist es üblich, ein finales Paket zu senden, das anzeigt, dass die Event‑Source das Senden aller Pakete abgeschlossen hat. Das folgende Beispiel zeigt, wie der Browser mit einem Server kommunizieren kann, der Server‑Side‑Events unterstützt. ```html {title="sse-example.html"} Fruits:
    ``` Und die Nachrichten schrittweise an den Client senden: ```cs {title="Controller/MyController.cs"} public class MyController { [RouteGet("/event-source")] public async Task ServerEventsResponse(HttpRequest request) { var serverEvents = await request.GetEventSourceAsync (); string[] fruits = new[] { "Apple", "Banana", "Watermelon", "Tomato" }; foreach (string fruit in fruits) { await serverEvents.SendAsync(fruit); await Task.Delay(1500); } return await serverEvents.CloseAsync(); } } ``` Beim Ausführen dieses Codes erwarten wir ein Ergebnis, das dem Folgenden ähnelt: ## Auflösen von proxied IPs und Hosts Sisk kann mit Proxies verwendet werden, sodass IP‑Adressen im Austausch zwischen Client und Proxy durch den Proxy‑Endpunkt ersetzt werden können. Sie können eigene Resolver in Sisk mit [forwarding resolvers](https://docs.sisk-framework.org/de/docs/advanced/forwarding-resolvers.md) definieren. ## Header-Codierung Header‑Codierung kann bei manchen Implementierungen problematisch sein. Unter Windows werden UTF‑8‑Header nicht unterstützt, daher wird ASCII verwendet. Sisk verfügt über einen eingebauten Codierungs‑Konverter, der beim Dekodieren falsch codierter Header nützlich sein kann. Dieser Vorgang ist kostenintensiv und standardmäßig deaktiviert, kann jedoch über [HttpServerConfiguration.NormalizeHeadersEncodings](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.NormalizeHeadersEncodings.md) aktiviert werden. --- # Antworten Source: https://docs.sisk-framework.org/de/docs/fundamentals/responses.html Antworten repräsentieren Objekte, die HTTP‑Antworten auf HTTP‑Anfragen sind. Sie werden vom Server an den Client gesendet, um auf die Anforderung einer Ressource, Seite, Dokuments, Datei oder eines anderen Objekts zu reagieren. Eine HTTP‑Antwort besteht aus Status, Headern und Inhalt. In diesem Dokument zeigen wir Ihnen, wie Sie HTTP‑Antworten mit Sisk entwerfen. ## Festlegen eines HTTP‑Status Die HTTP‑Statusliste ist seit HTTP/1.0 unverändert, und Sisk unterstützt alle davon. ```cs HttpResponse res = new HttpResponse(); res.Status = System.Net.HttpStatusCode.Accepted; // 202 ``` Oder mit Fluent‑Syntax: ```cs new HttpResponse() .WithStatus(200) // oder .WithStatus(HttpStatusCode.Ok) // oder .WithStatus(HttpStatusInformation.Ok); ``` Sie können die vollständige Liste der verfügbaren `HttpStatusCode` [hier](https://learn.microsoft.com/pt-br/dotnet/api/system.net.httpstatuscode) einsehen. Sie können auch Ihren eigenen Statuscode bereitstellen, indem Sie die Struktur [HttpStatusInformation](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpStatusInformation.md) verwenden. ## Body und Content‑Type Sisk unterstützt native .NET‑Inhaltsobjekte, um den Body in Antworten zu senden. Sie können die Klasse [StringContent](https://learn.microsoft.com/pt-br/dotnet/api/system.net.http.stringcontent) verwenden, um beispielsweise eine JSON‑Antwort zu senden: ```cs HttpResponse res = new HttpResponse(); res.Content = new StringContent(myJson, Encoding.UTF8, "application/json"); ``` Der Server versucht stets, den `Content-Length` aus dem von Ihnen definierten Inhalt zu berechnen, sofern Sie ihn nicht explizit in einem Header festgelegt haben. Kann der Server den `Content-Length`‑Header nicht implizit aus dem Antwortinhalt ableiten, wird die Antwort mit Chunked‑Encoding gesendet. Sie können die Antwort auch streamen, indem Sie ein [StreamContent](https://learn.microsoft.com/pt-br/dotnet/api/system.net.http.streamcontent) senden oder die Methode [GetResponseStream](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.GetResponseStream.md) verwenden. ## Antwort‑Header Sie können Header, die Sie in der Antwort senden, hinzufügen, bearbeiten oder entfernen. Das folgende Beispiel zeigt, wie Sie eine Weiterleitungsantwort an den Client senden. ```cs HttpResponse res = new HttpResponse(); res.Status = HttpStatusCode.Moved; res.Headers.Add(HttpKnownHeaderNames.Location, "/login"); ``` Oder mit Fluent‑Syntax: ```cs new HttpResponse(301) .WithHeader("Location", "/login"); ``` Wenn Sie die Methode [Add](https://docs.sisk-framework.org/api/Sisk.Core.Entity.HttpHeaderCollection.Add.md) von `HttpHeaderCollection` verwenden, fügen Sie einen Header zur Anfrage hinzu, ohne die bereits gesendeten zu verändern. Die Methode [Set](https://docs.sisk-framework.org/api/Sisk.Core.Entity.HttpHeaderCollection.Set.md) ersetzt Header mit demselben Namen durch den angegebenen Wert. Der Indexer von `HttpHeaderCollection` ruft intern die `Set`‑Methode auf, um die Header zu ersetzen. Sie können Header‑Werte auch über die Methode [GetHeaderValue](https://docs.sisk-framework.org/api/Sisk.Core.Entity.HttpHeaderCollection.GetHeaderValue.md) abrufen. Diese Methode hilft beim Erhalten von Werten sowohl aus den Antwort‑Headern als auch aus den Inhalts‑Headern (falls ein Inhalt gesetzt ist). ```cs // Gibt den Wert des "Content-Type"-Headers zurück und prüft sowohl response.Headers als auch response.Content.Headers string? contentType = response.GetHeaderValue("Content-Type"); ``` ## Cookies senden Sisk bietet Methoden, die das Definieren von Cookies im Client erleichtern. Cookies, die mit dieser Methode gesetzt werden, sind bereits URL‑kodiert und entsprechen dem RFC‑6265‑Standard. ```cs HttpResponse res = new HttpResponse(); res.SetCookie("cookie-name", "cookie-value"); ``` Oder mit Fluent‑Syntax: ```cs new HttpResponse(301) .WithCookie("cookie-name", "cookie-value", expiresAt: DateTime.Now.Add(TimeSpan.FromDays(7))); ``` Es gibt weitere [ausführlichere Versionen](https://docs.sisk-framework.org/api/Sisk.Core.Helpers.CookieHelper.SetCookie.md) derselben Methode. ## Chunked‑Antworten Sie können die Transfer‑Codierung auf Chunked setzen, um große Antworten zu senden. ```cs HttpResponse res = new HttpResponse(); res.SendChunked = true; ``` Bei Verwendung von Chunked‑Encoding wird der `Content-Length`‑Header automatisch weggelassen. ## Antwort‑Stream Antwort‑Streams sind ein verwalteter Weg, um Antworten segmentiert zu senden. Sie stellen eine niedrigere Ebene dar als die Verwendung von `HttpResponse`‑Objekten, da Sie Header und Inhalt manuell senden und anschließend die Verbindung schließen müssen. Dieses Beispiel öffnet einen schreibgeschützten Stream für die Datei, kopiert den Stream in den Antwort‑Ausgabestream und lädt die gesamte Datei nicht in den Speicher. Das kann beim Bereitstellen mittelgroßer oder großer Dateien nützlich sein. ```cs // erhält den Antwort‑Ausgabestream using var fileStream = File.OpenRead("my-big-file.zip"); var responseStream = request.GetResponseStream(); // setzt die Antwort‑Codierung auf Chunked‑Encoding // außerdem sollten Sie keinen Content‑Length‑Header senden, wenn Sie // Chunked‑Encoding verwenden responseStream.SendChunked = true; responseStream.SetStatus(200); responseStream.SetHeader(HttpKnownHeaderNames.ContentType, contentType); // kopiert den Dateistream in den Antwort‑Ausgabestream fileStream.CopyTo(responseStream.ResponseStream); // schließt den Stream return responseStream.Close(); ``` ## GZip-, Deflate- und Brotli‑Kompression Sie können in Sisk Antworten mit komprimiertem Inhalt senden, indem Sie HTTP‑Inhalte komprimieren. Kapseln Sie zunächst Ihr [HttpContent](https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpcontent)‑Objekt in einen der nachfolgenden Kompressor, um die komprimierte Antwort an den Client zu senden. ```cs router.MapGet("/hello.html", request => { string myHtml = "..."; return new HttpResponse () { Content = new GZipContent(new HtmlContent(myHtml)), // oder Content = new BrotliContent(new HtmlContent(myHtml)), // oder Content = new DeflateContent(new HtmlContent(myHtml)), }; }); ``` Sie können diese komprimierten Inhalte auch mit Streams verwenden. ```cs router.MapGet("/archive.zip", request => { // kein "using" hier verwenden. Der HttpServer verwirft Ihren Inhalt // nach dem Senden der Antwort. var archive = File.OpenRead("/path/to/big-file.zip"); return new HttpResponse () { Content = new GZipContent(archive) } }); ``` Die `Content-Encoding`‑Header werden automatisch gesetzt, wenn diese Inhalte verwendet werden. ## Automatische Kompression Es ist möglich, HTTP‑Antworten automatisch zu komprimieren, indem die Eigenschaft [EnableAutomaticResponseCompression](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.EnableAutomaticResponseCompression.md) aktiviert wird. Diese Eigenschaft kapselt den Antwortinhalt des Routers automatisch in einen komprimierbaren Inhalt, der vom Request akzeptiert wird, sofern die Antwort nicht von einem [CompressedContent](https://docs.sisk-framework.org/api/Sisk.Core.Http.CompressedContent.md) erbt. Für eine Anfrage wird nur ein komprimierbarer Inhalt ausgewählt, basierend auf dem `Accept-Encoding`‑Header, der in folgender Reihenfolge geprüft wird: - [BrotliContent](https://docs.sisk-framework.org/api/Sisk.Core.Http.BrotliContent.md) (br) - [GZipContent](https://docs.sisk-framework.org/api/Sisk.Core.Http.GZipContent.md) (gzip) - [DeflateContent](https://docs.sisk-framework.org/api/Sisk.Core.Http.DeflateContent.md) (deflate) Wenn die Anfrage angibt, dass sie eines dieser Kompressionsverfahren akzeptiert, wird die Antwort automatisch komprimiert. ## Implizite Antworttypen Sie können andere Rückgabetypen als `HttpResponse` verwenden, müssen jedoch den Router konfigurieren, wie er mit jedem Objekttyp umgehen soll. Das Konzept besteht darin, immer einen Referenztyp zurückzugeben und ihn in ein gültiges `HttpResponse`‑Objekt zu verwandeln. Routen, die `HttpResponse` zurückgeben, durchlaufen keine Konvertierung. Werttypen (Strukturen) können nicht als Rückgabetyp verwendet werden, weil sie nicht mit dem [RouterCallback](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouterCallback.md) kompatibel sind; sie müssen in ein `ValueResult` gewrappt werden, um in Handlern verwendet zu werden. Betrachten Sie das folgende Beispiel eines Router‑Moduls, das `HttpResponse` nicht als Rückgabetyp nutzt: ```cs [RoutePrefix("/users")] public class UsersController : RouterModule { public List Users = new List(); [RouteGet] public IEnumerable Index(HttpRequest request) { return Users.ToArray(); } [RouteGet("")] public User View(HttpRequest request) { int id = request.RouteParameters["id"].GetInteger(); User dUser = Users.First(u => u.Id == id); return dUser; } [RoutePost] public ValueResult Create(HttpRequest request) { User fromBody = request.GetJsonContent()!; Users.Add(fromBody); return true; } } ``` Damit muss nun im Router definiert werden, wie mit jedem Objekttyp verfahren wird. Objekte sind stets das erste Argument des Handlers und der Ausgabetyp muss ein gültiges `HttpResponse` sein. Außerdem sollten die Ausgabebestandteile einer Route niemals `null` sein. Für `ValueResult`‑Typen ist es nicht nötig, anzugeben, dass das Eingabeobjekt ein `ValueResult` ist – nur `T`, da `ValueResult` ein Objekt ist, das von seiner ursprünglichen Komponente reflektiert wird. Die Zuordnung der Typen vergleicht nicht, was registriert wurde, mit dem Typ des vom Router‑Callback zurückgegebenen Objekts. Stattdessen wird geprüft, ob der Typ des Router‑Ergebnisses dem registrierten Typ zuweisbar ist. Die Registrierung eines Handlers vom Typ `Object` fällt auf alle zuvor nicht validierten Typen zurück. Die Einfügereihenfolge der Wert‑Handler ist ebenfalls wichtig: Ein `Object`‑Handler ignoriert alle anderen typ‑spezifischen Handler. Registrieren Sie daher spezifische Wert‑Handler zuerst, um die Reihenfolge zu sichern. ```cs Router r = new Router(); r.MapInstance(new UsersController()); r.RegisterValueHandler(apiResult => { return new HttpResponse() { Status = apiResult.Success ? HttpStatusCode.OK : HttpStatusCode.BadRequest, Content = apiResult.GetHttpContent(), Headers = apiResult.GetHeaders() }; }); r.RegisterValueHandler(bvalue => { return new HttpResponse() { Status = bvalue ? HttpStatusCode.OK : HttpStatusCode.BadRequest }; }); r.RegisterValueHandler>(enumerableValue => { return new HttpResponse(string.Join("\n", enumerableValue)); }); // Die Registrierung eines Wert‑Handlers vom Typ object muss der letzte // Wert‑Handler sein, der als Fallback verwendet wird r.RegisterValueHandler(fallback => { return new HttpResponse() { Status = HttpStatusCode.OK, Content = JsonContent.Create(fallback) }; }); ``` ## Verzögerte Aktionen Wenn eine Anfrage den Router erreicht, durchläuft sie zuerst die [Request‑Handler](https://docs.sisk-framework.org/de/docs/fundamentals/request-handlers.md), wird in der Router‑Aktion verarbeitet und anschließend von den Post‑Execution‑Request‑Handlern. Das Ergebnis der Router‑Aktion wird an die Wert‑Handler übergeben, und das Ergebnis des Wert‑Handlers wird dem Client als Antwort gesendet. Dieser Lebenszyklus findet innerhalb eines asynchronen Kontextes statt. Dieser asynchrone Kontext stellt Variablen bereit, die der Benutzer in den [HttpContext‑Bag](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpContext.md) einfügen kann, um Daten zwischen Handlern und der Router‑Aktion zu teilen. Der von der Router‑Aktion zurückgegebene Wert wird diesem asynchronen Kontext hinzugefügt und kann von den Wert‑Handlern abgerufen werden. Verzögerte Aktionen sind Aktionen, die immer am Ende des Zyklus ausgeführt werden, nachdem die Antwort an den Client gesendet wurde, jedoch noch innerhalb desselben asynchronen Kontextes. Diese Aktionen können verwendet werden, um langlaufende Aufgaben auszuführen, die nicht abgeschlossen sein müssen, um eine Antwort an den Client zu senden, z. B. das Speichern von Logs, das Aktualisieren der Datenbank, das Versenden von E‑Mails usw. Ausnahmen werden weiterhin in verzögerten Aktionen abgefangen und wie jede andere Ausnahme im Anforderungs‑Lebenszyklus behandelt. Der Unterschied besteht darin, dass der Client bereits eine Antwort erhalten hat, sodass die Ausnahme von der Standard‑Fehlerbehandlung verarbeitet wird. Verzögern Sie die Ausführung einer Aktion mit der Methode [HttpContext.EnqueueDeferredAction](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpContext.EnqueueDeferredAction.md). Die Methode erhält eine asynchrone Funktion, die die auszuführende Aktion repräsentiert, sowie ein optionales Timeout, um die Ausführungszeit der Aktion zu begrenzen. Wird die Aktion nicht innerhalb des Zeitlimits abgeschlossen, wird sie abgebrochen. ```csharp [RoutePost("/send-mail")] public HttpResponse SendMail(HttpRequest request) { string to = request.Query["to"].GetString(); string subject = request.Query["subject"].GetString(); string body = request.Query["body"].GetString(); if (string.IsNullOrWhiteSpace(to) || string.IsNullOrWhiteSpace(subject) || string.IsNullOrWhiteSpace(body)) { throw new ApiException("Missing required parameters."); } // plant eine langlaufende Aktion, die nach dem Senden der Antwort an den Client // aber noch innerhalb desselben asynchronen Kontextes der Anfrage ausgeführt wird request.Context.EnqueueDeferredAction(async (ct) => { await EmailService.SendEmailAsync(to, subject, body); }, timeout: TimeSpan.FromSeconds(30)); return new HttpResponse() { Status = 200, Content = new StringContent("Sending the email...") }; } ``` ## Hinweis zu aufzählbaren Objekten und Arrays Implizite Antwortobjekte, die [IEnumerable](https://learn.microsoft.com/pt-br/dotnet/api/system.collections.ienumerable?view=net-8.0) implementieren, werden über die Methode `ToArray()` in den Speicher geladen, bevor sie durch einen definierten Wert‑Handler konvertiert werden. Dafür wird das `IEnumerable`‑Objekt in ein Objekt‑Array umgewandelt, und der Antwort‑Konverter erhält stets ein `Object[]` statt des ursprünglichen Typs. Betrachten Sie das folgende Szenario: ```csharp using var host = HttpServer.CreateBuilder(12300) .UseRouter(r => { r.RegisterValueHandler>(stringEnumerable => { return new HttpResponse("String array:\n" + string.Join("\n", stringEnumerable)); }); r.RegisterValueHandler>(stringEnumerable => { return new HttpResponse("Object array:\n" + string.Join("\n", stringEnumerable)); }); r.MapGet("/", request => { return (IEnumerable)["hello", "world"]; }); }) .Build(); ``` Im obigen Beispiel wird der `IEnumerable`‑Konverter **nie aufgerufen**, weil das Eingabeobjekt immer ein `Object[]` ist und nicht in ein `IEnumerable` konvertierbar ist. Der untenstehende Konverter, der ein `IEnumerable` erhält, wird jedoch aufgerufen, da sein Wert kompatibel ist. Wenn Sie tatsächlich den Typ des zu enumerierenden Objekts behandeln müssen, benötigen Sie Reflection, um den Typ des Sammlungselements zu ermitteln. Alle aufzählbaren Objekte (Listen, Arrays und Collections) werden vom HTTP‑Antwort‑Konverter in ein Objekt‑Array umgewandelt. Werte, die [IAsyncEnumerable](https://learn.microsoft.com/pt-br/dotnet/api/system.collections.generic.iasyncenumerable-1?view=net-8.0) implementieren, werden vom Server automatisch verarbeitet, wenn die Eigenschaft [ConvertIAsyncEnumerableIntoEnumerable](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ConvertIAsyncEnumerableIntoEnumerable.md) aktiviert ist – analog zu dem, was bei `IEnumerable` geschieht. Diese Option ist standardmäßig in `HttpServerConfiguration` aktiviert; eine asynchrone Enumeration wird in einen blockierenden Enumerator umgewandelt und anschließend in ein synchrones Objekt‑Array. Deaktivieren Sie sie nur, wenn Sie Ihren eigenen Wert‑Handler oder eine Streaming‑Antwort‑Strategie für asynchrone Sequenzen bereitstellen. --- # Protokollierung Source: https://docs.sisk-framework.org/de/docs/features/logging.html Sie können Sisk so konfigurieren, dass Zugriffs- und Fehlermeldungen automatisch geschrieben werden. Es ist möglich, Log‑Rotation, Erweiterungen und Häufigkeit zu definieren. Die [LogStream](https://docs.sisk-framework.org/api/Sisk.Core.Http.LogStream.md)-Klasse bietet eine asynchrone Methode zum Schreiben von Logs und hält sie in einer await‑fähigen Schreibwarteschlange. Die `LogStream`‑Klasse implementiert `IAsyncDisposable` und stellt sicher, dass alle ausstehenden Logs geschrieben werden, bevor der Stream geschlossen wird. In diesem Artikel zeigen wir Ihnen, wie Sie die Protokollierung für Ihre Anwendung konfigurieren. ## Dateibasierte Zugriffsprotokolle Logs zu Dateien öffnen die Datei, schreiben den Zeilentext und schließen die Datei anschließend für jede geschriebene Zeile. Dieses Verfahren wurde übernommen, um die Schreib‑Reaktionsfähigkeit in den Logs zu erhalten. ```cs {title="Program.cs"} class Program { static async Task Main(string[] args) { using var app = HttpServer.CreateBuilder() .UseConfiguration(config => { config.AccessLogsStream = new LogStream("logs/access.log"); }) .Build(); ... await app.StartAsync(); } } ``` Der obige Code schreibt alle eingehenden Anfragen in die Datei `logs/access.log`. Beachten Sie, dass die Datei automatisch erstellt wird, falls sie nicht existiert, das übergeordnete Verzeichnis jedoch nicht. Es ist nicht nötig, das Verzeichnis `logs/` manuell anzulegen, da die LogStream‑Klasse es automatisch erstellt. ## Stream-basierte Protokollierung Sie können Log‑Dateien in Instanzen von `TextWriter`‑Objekten schreiben, z. B. `Console.Out`, indem Sie ein `TextWriter`‑Objekt im Konstruktor übergeben: ```cs {title="Program.cs"} using var app = HttpServer.CreateBuilder() .UseConfiguration(config => { config.AccessLogsStream = new LogStream(Console.Out); }) .Build(); ``` Für jede im stream‑basierten Log geschriebene Nachricht wird die Methode `TextWriter.Flush()` aufgerufen. ## Formatierung des Zugriffsprotokolls Sie können das Zugriffsprotokollformat mit vordefinierten Variablen anpassen. Betrachten Sie die folgende Zeile: ```cs config.AccessLogsFormat = "%dd/%dmm/%dy %tH:%ti:%ts %tz %ls %ri %rs://%ra%rz%rq [%sc %sd] %lin -> %lou in %lmsms [%{user-agent}]"; ``` Sie wird eine Meldung wie folgt schreiben: 29/mar./2023 15:21:47 -0300 Executed ::1 http://localhost:5555/ [200 OK] 689B -> 707B in 84ms [Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/111.0.0.0 Safari/537.36] Sie können Ihre Log‑Datei nach dem in der Tabelle beschriebenen Format formatieren: | Wert | Was es darstellt | Beispiel | |--------------------|------------------------------------------------------------------------------|----------------------------------------| | %dd | Tag des Monats (zweistellig formatiert) | 05 | | %dmmm | Vollständiger Name des Monats | July | | %dmm | Abgekürzter Name des Monats (drei Buchstaben) | Jul | | %dm | Monatszahl (zweistellig formatiert) | 07 | | %dy | Jahr (vierstellig formatiert) | 2023 | | %th | Stunde im 12‑Stunden‑Format | 03 | | %tH | Stunde im 24‑Stunden‑Format (HH) | 15 | | %ti | Minuten (zweistellig formatiert) | 30 | | %ts | Sekunden (zweistellig formatiert) | 45 | | %tm | Millisekunden (dreistellig formatiert) | 123 | | %tz | Zeitzonenoffset (Gesamtstunden in UTC) | +03:00 | | %ri | Remote‑IP‑Adresse des Clients | 192.168.1.100 | | %rm | HTTP‑Methode (Großschreibung) | GET | | %rs | URI‑Schema (http/https) | https | | %ra | URI‑Authority (Domain) | example.com | | %rh | Host der Anfrage | www.example.com | | %rp | Port der Anfrage | 443 | | %rz | Pfad der Anfrage | /path/to/resource | | %rq | Abfragezeichenfolge | ?key=value&another=123 | | %sc | HTTP‑Antwortstatuscode | 200 | | %sd | Beschreibung des HTTP‑Antwortstatus | OK | | %lin | Menschlich lesbare Größe der Anfrage | 1.2 KB | | %linr | Rohgröße der Anfrage (Bytes) | 1234 | | %lou | Menschlich lesbare Größe der Antwort | 2.5 KB | | %lour | Rohgröße der Antwort (Bytes) | 2560 | | %lms | Verstrichene Zeit in Millisekunden | 120 | | %ls | Ausführungsstatus | Executed | | %{header-name} | Stellt den Header `header-name` der Anfrage dar. | `Mozilla/5.0 (platform; rv:gecko [...]` | | %{:header-name} | Stellt den Header `header-name` der Antwort dar. | `application/json` | Sie können außerdem `HttpServerConfiguration.DefaultAccessLogFormat` verwenden, um das Standard‑Zugriffsprotokollformat zu nutzen. ## Rotierende Protokolle Sie können den HTTP‑Server so konfigurieren, dass Log‑Dateien zu einer komprimierten .gz‑Datei rotiert werden, sobald sie eine bestimmte Größe erreichen. Die Größe wird periodisch anhand der von Ihnen definierten Schwelle geprüft. ```cs LogStream errorLog = new LogStream("logs/error.log") .ConfigureRotatingPolicy( maximumSize: 64 * SizeHelper.UnitMb, dueTime: TimeSpan.FromHours(6)); ``` Der obige Code prüft alle sechs Stunden, ob die Datei des LogStreams sein 64 MB‑Limit erreicht hat. Falls ja, wird die Datei zu einer .gz‑Datei komprimiert und anschließend `access.log` bereinigt. Während dieses Vorgangs ist das Schreiben in die Datei gesperrt, bis die Datei komprimiert und bereinigt ist. Alle Zeilen, die in diesem Zeitraum geschrieben werden sollen, befinden sich in einer Warteschlange, die auf das Ende der Kompression wartet. Diese Funktion arbeitet nur mit dateibasierten LogStreams. ## Fehlerprotokollierung Wenn ein Server keine Fehler an den Debugger wirft, leitet er die Fehler zum Log‑Schreiben weiter, sofern welche vorhanden sind. Sie können das Fehler‑Schreiben konfigurieren mit: ```cs config.ThrowExceptions = false; config.ErrorsLogsStream = new LogStream("error.log"); ``` Diese Eigenschaft schreibt nur dann etwas in das Log, wenn der Fehler nicht vom Callback oder der [Router.CallbackErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.CallbackErrorHandler.md)-Eigenschaft erfasst wird. Der vom Server geschriebene Fehler protokolliert stets Datum und Uhrzeit, die Anforderungs‑Header (nicht den Body), den Fehler‑Stacktrace und, falls vorhanden, den Stacktrace der inneren Ausnahme. ## Andere Protokollierungsinstanzen Ihre Anwendung kann null oder mehrere LogStreams besitzen; es gibt keine Begrenzung, wie viele Log‑Kanäle sie haben kann. Daher ist es möglich, das Log Ihrer Anwendung in eine andere Datei als das Standard‑AccessLog oder ErrorLog zu leiten. ```cs LogStream appMessages = new LogStream("messages.log"); appMessages.WriteLine("Application started at {0}", DateTime.Now); ``` ## Erweiterung von LogStream Sie können die `LogStream`‑Klasse erweitern, um benutzerdefinierte Formate zu schreiben, die mit der aktuellen Sisk‑Log‑Engine kompatibel sind. Das nachstehende Beispiel ermöglicht das Schreiben farbiger Meldungen in die Konsole über die Spectre.Console‑Bibliothek: ```cs {title="CustomLogStream.cs"} public class CustomLogStream : LogStream { protected override void WriteLineInternal(string line) { base.WriteLineInternal($"[{DateTime.Now:g}] {line}"); } } ``` Eine weitere Möglichkeit, automatisch benutzerdefinierte Logs für jede Anfrage/Antwort zu schreiben, besteht darin, einen [HttpServerHandler](https://docs.sisk-framework.org/api/Sisk.Core.Http.Handlers.HttpServerHandler.md) zu erstellen. Das nachstehende Beispiel ist etwas umfangreicher. Es schreibt den Body von Anfrage und Antwort als JSON in die Konsole. Es kann allgemein beim Debuggen von Anfragen nützlich sein. Dieses Beispiel nutzt ContextBag und HttpServerHandler. ```cs {title="Program.cs"} class Program { static async Task Main(string[] args) { var app = HttpServer.CreateBuilder(host => { host.UseListeningPort(5555); host.UseHandler(); }); app.Router.MapAny("/json", request => { return new HttpResponse() .WithContent(JsonContent.Create(new { method = request.Method.Method, path = request.Path, specialMessage = "Hello, world!!" })); }); await app.StartAsync(); } } ``` ```cs {title="JsonMessageHandler.cs"} class JsonMessageHandler : HttpServerHandler { protected override void OnHttpRequestOpen(HttpRequest request) { if (request.Method != HttpMethod.Get && request.Headers["Content-Type"]?.Contains("json", StringComparison.InvariantCultureIgnoreCase) == true) { // Zu diesem Zeitpunkt ist die Verbindung geöffnet und der Client hat den Header gesendet, der angibt, // dass der Inhalt JSON ist. Die nachfolgende Zeile liest den Inhalt und lässt ihn in der Anfrage gespeichert. // // Wenn der Inhalt nicht in der Anforderungsaktion gelesen wird, kann die GC den Inhalt wahrscheinlich sammeln, // nachdem die Antwort an den Client gesendet wurde, sodass der Inhalt nach dem Schließen der Antwort nicht mehr verfügbar ist. // _ = request.RawBody; // Hinweis im Kontext hinzufügen, dass diese Anfrage einen JSON-Body enthält request.Bag.Add("IsJsonRequest", true); } } protected override async void OnHttpRequestClose(HttpServerExecutionResult result) { string? requestJson = null, responseJson = null, responseMessage; if (result.Request.Bag.ContainsKey("IsJsonRequest")) { // Formatiert das JSON mithilfe der CypherPotato.LightJson-Bibliothek neu var content = result.Request.Body; requestJson = JsonValue.Deserialize(content, new JsonOptions() { WriteIndented = true }).ToString(); } if (result.Response is { } response) { var content = response.Content; responseMessage = $"{(int)response.Status} {HttpStatusInformation.GetStatusCodeDescription(response.Status)}"; if (content is HttpContent httpContent && // prüfen, ob die Antwort JSON ist httpContent.Headers.ContentType?.MediaType?.Contains("json", StringComparison.InvariantCultureIgnoreCase) == true) { string json = await httpContent.ReadAsStringAsync(); responseJson = JsonValue.Deserialize(json, new JsonOptions() { WriteIndented = true }).ToString(); } } else { // holt den internen Serververarbeitungsstatus responseMessage = result.Status.ToString(); } StringBuilder outputMessage = new StringBuilder(); if (requestJson != null) { outputMessage.AppendLine("-----"); outputMessage.AppendLine($">>> {result.Request.Method} {result.Request.Path}"); if (requestJson is not null) outputMessage.AppendLine(requestJson); } outputMessage.AppendLine($"<<< {responseMessage}"); if (responseJson is not null) outputMessage.AppendLine(responseJson); outputMessage.AppendLine("-----"); await Console.Out.WriteLineAsync(outputMessage.ToString()); } } ``` --- # Server Sent Events Source: https://docs.sisk-framework.org/de/docs/features/server-sent-events.html Sisk unterstützt das Senden von Nachrichten über Server Sent Events von Haus aus. Sie können flüchtige und dauerhafte Verbindungen erstellen, die Verbindungen zur Laufzeit abrufen und verwenden. Diese Funktion hat einige von Browsern auferlegte Einschränkungen, wie das Senden nur von Textnachrichten und die Unfähigkeit, eine Verbindung dauerhaft zu schließen. Eine serverseitig geschlossene Verbindung führt dazu, dass der Client alle 5 Sekunden (bei manchen Browsern 3 Sekunden) periodisch versucht, die Verbindung wiederherzustellen. Diese Verbindungen sind nützlich, um Ereignisse vom Server zum Client zu senden, ohne dass der Client die Informationen jedes Mal anfordern muss. ## Erstellen einer SSE-Verbindung Eine SSE-Verbindung funktioniert wie eine reguläre HTTP-Anfrage, jedoch wird die Verbindung, anstatt nach dem Senden einer Antwort sofort zu schließen, offen gehalten, um Nachrichten zu senden. Durch Aufrufen der Methode [HttpRequest.GetEventSource()](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.GetEventSource.md) wird die Anfrage in einen Wartezustand versetzt, während die SSE-Instanz erstellt wird. ```cs r.MapGet("/", (req) => { using var sse = req.GetEventSource(); sse.Send("Hello, world!"); return sse.Close(); }); ``` Im obigen Code erstellen wir eine SSE-Verbindung und senden eine „Hello, world“-Nachricht, anschließend schließen wir die SSE-Verbindung serverseitig. > [!NOTE] > Beim Schließen einer serverseitigen Verbindung versucht der Client standardmäßig, erneut zu verbinden, und die Verbindung wird neu gestartet, wobei die Methode endlos erneut ausgeführt wird. > > Es ist üblich, vom Server aus eine Beendigungsnachricht zu senden, sobald die Verbindung vom Server geschlossen wird, um zu verhindern, dass der Client erneut versucht, sich zu verbinden. ## Anhängen von Headern Falls Sie Header senden müssen, können Sie die Methode [HttpRequestEventSource.AppendHeader](https://docs.sisk-framework.org/api/Sisk.Core.Http.Streams.HttpRequestEventSource.AppendHeader.md) verwenden, bevor Sie Nachrichten senden. ```cs r.MapGet("/", (req) => { using var sse = req.GetEventSource(); sse.AppendHeader("Header-Key", "Header-value"); sse.Send("Hello!"); return sse.Close(); }); ``` Beachten Sie, dass die Header vor dem Senden von Nachrichten gesendet werden müssen. ## Wait-For-Fail-Verbindungen Verbindungen werden normalerweise beendet, wenn der Server aufgrund einer möglichen clientseitigen Trennung keine Nachrichten mehr senden kann. In diesem Fall wird die Verbindung automatisch beendet und die Instanz der Klasse verworfen. Selbst bei einer erneuten Verbindung funktioniert die Instanz der Klasse nicht mehr, da sie mit der vorherigen Verbindung verknüpft ist. In manchen Situationen benötigen Sie diese Verbindung später und möchten sie nicht über die Callback‑Methode der Route verwalten. Dafür können wir die SSE-Verbindungen mit einem Bezeichner identifizieren und später, auch außerhalb des Callback der Route, abrufen. Zusätzlich markieren wir die Verbindung mit [WaitForFail](https://docs.sisk-framework.org/api/Sisk.Core.Http.Streams.HttpRequestEventSource.WaitForFail.md), um die Route nicht zu beenden und die Verbindung automatisch zu schließen. Eine SSE-Verbindung im `WaitForFail` wartet auf einen Sendefehler, der durch eine Trennung verursacht wurde, oder darauf, dass die konfigurierte Leerlauftoleranz abläuft, bevor die Route fortgesetzt und die Verbindung geschlossen wird. ```cs r.MapGet("/", (req) => { using var sse = req.GetEventSource("my-index-connection"); sse.WaitForFail(TimeSpan.FromSeconds(15)); // wait for 15 seconds without any message before terminating the connection return sse.Close(); }); ``` Die obige Methode erstellt die Verbindung, verwaltet sie und wartet auf eine Trennung oder einen Fehler. ```cs HttpRequestEventSource? evs = server.EventSources.GetByIdentifier("my-index-connection"); if (evs != null) { // the connection is still alive evs.Send("Hello again!"); } ``` Und das obige Snippet versucht, die neu erstellte Verbindung zu finden, und falls sie existiert, wird eine Nachricht an sie gesendet. Alle aktiven, identifizierten Serververbindungen sind in der Sammlung [HttpServer.EventSources](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServer.EventSources.md) verfügbar. Diese Sammlung speichert nur aktive und identifizierte Verbindungen. Geschlossene Verbindungen werden aus der Sammlung entfernt. > [!NOTE] > Es ist wichtig zu beachten, dass Keep‑Alive ein von Komponenten festgelegtes Limit hat, die in unkontrollierbarer Weise mit Sisk verbunden sein können, wie ein Web‑Proxy, ein HTTP‑Kernel oder ein Netzwerktreiber, und diese schließen Leerlaufverbindungen nach einer bestimmten Zeit. > > Daher ist es wichtig, die Verbindung offen zu halten, indem periodische Pings gesendet oder die maximale Zeit bis zum Schließen der Verbindung verlängert wird. Lesen Sie den nächsten Abschnitt, um das Senden periodischer Pings besser zu verstehen. ## Einrichtung der Ping-Policy für Verbindungen Die Ping-Policy ist ein automatisierter Weg, periodische Nachrichten an Ihren Client zu senden. Diese Funktion ermöglicht es dem Server zu erkennen, wann der Client die Verbindung getrennt hat, ohne die Verbindung unbegrenzt offen halten zu müssen. ```cs [RouteGet("/sse")] public async Task Events(HttpRequest request) { using var sse = await request.GetEventSourceAsync("user-events"); sse.WithPing(ping => { ping.DataMessage = "ping-message"; ping.Interval = TimeSpan.FromSeconds(5); ping.Start(); }); await sse.WaitForFailAsync(TimeSpan.FromMinutes(10)); return await sse.CloseAsync(); } ``` Im obigen Code wird alle 5 Sekunden eine neue Ping-Nachricht an den Client gesendet. Dadurch bleibt die TCP-Verbindung aktiv und wird nicht wegen Inaktivität geschlossen. Außerdem wird die Verbindung automatisch geschlossen, wenn das Senden einer Nachricht fehlschlägt, wodurch die genutzten Ressourcen freigegeben werden. Verwenden Sie [SendAsync](https://docs.sisk-framework.org/api/Sisk.Core.Http.Streams.HttpRequestEventSource.SendAsync.md) und [CloseAsync](https://docs.sisk-framework.org/api/Sisk.Core.Http.Streams.HttpRequestEventSource.CloseAsync.md) in asynchronen Routen. Wenn Sie vor dem Schließen ausstehende Ereignisse verwerfen müssen, rufen Sie [Cancel](https://docs.sisk-framework.org/api/Sisk.Core.Http.Streams.HttpRequestEventSource.Cancel.md) auf. ## Abfragen von Verbindungen Sie können nach aktiven Verbindungen suchen, indem Sie ein Prädikat auf den Verbindungsbezeichner anwenden, um beispielsweise zu broadcasten. ```cs HttpRequestEventSource[] evs = server.EventSources.Find(es => es.StartsWith("my-connection-")); foreach (HttpRequestEventSource e in evs) { e.Send("Broadcasting to all event sources that starts with 'my-connection-'"); } ``` Sie können auch die Methode [All](https://docs.sisk-framework.org/api/Sisk.Core.Http.Streams.HttpEventSourceCollection.All.md) verwenden, um alle aktiven SSE-Verbindungen zu erhalten. --- # WebSockets Source: https://docs.sisk-framework.org/de/docs/features/websockets.html Sisk unterstützt ebenfalls WebSockets, zum Beispiel das Empfangen und Senden von Nachrichten an den Client. Diese Funktion funktioniert in den meisten Browsern einwandfrei, ist aber in Sisk noch experimentell. Bitte melden Sie etwaige Fehler auf GitHub. ## Empfangen von Nachrichten WebSocket-Nachrichten werden in Reihenfolge empfangen und bis zur Verarbeitung durch `ReceiveMessageAsync` in einer Warteschlange gehalten. Diese Methode liefert keine Nachricht, wenn das Zeitlimit erreicht wird, die Operation abgebrochen wird oder der Client die Verbindung trennt. Nur ein Lese- und Schreibvorgang kann gleichzeitig stattfinden, daher ist es nicht möglich, während des Wartens auf eine Nachricht mit `ReceiveMessageAsync` an den verbundenen Client zu schreiben. ```cs router.MapGet("/connect", async (HttpRequest req) => { using var ws = await req.GetWebSocketAsync(); while (await ws.ReceiveMessageAsync(timeout: TimeSpan.FromSeconds(30)) is { } receivedMessage) { string msgText = receivedMessage.GetString(); Console.WriteLine("Received message: " + msgText); await ws.SendAsync("Hello!"); } return await ws.CloseAsync(); }); ``` ## Persistente Verbindung Das nachstehende Beispiel zeigt, wie Sie eine persistente WebSocket-Verbindung nutzen können, bei der Sie die Nachrichten empfangen, verarbeiten und die Verbindung anschließend schließen. ```cs router.MapGet("/connect", async (HttpRequest req) => { using var ws = await req.GetWebSocketAsync(); WebSocketMessage? msg; askName: await ws.SendAsync("What is your name?"); msg = await ws.ReceiveMessageAsync(); if (msg is null) return await ws.CloseAsync(); string name = msg.GetString(); if (string.IsNullOrEmpty(name)) { await ws.SendAsync("Please, insert your name!"); goto askName; } askAge: await ws.SendAsync("And your age?"); msg = await ws.ReceiveMessageAsync(); if (msg is null) return await ws.CloseAsync(); if (!Int32.TryParse(msg?.GetString(), out int age)) { await ws.SendAsync("Please, insert an valid number"); goto askAge; } await ws.SendAsync($"You're {name}, and you are {age} old."); return await ws.CloseAsync(); }); ``` ## Ping-Richtlinie Ähnlich wie die Ping-Richtlinie bei Server‑Sent‑Events können Sie auch eine Ping‑Richtlinie konfigurieren, um die TCP‑Verbindung bei Inaktivität offen zu halten. ```cs ws.PingPolicy.Start( dataMessage: "ping-message", interval: TimeSpan.FromSeconds(10)); ``` ## Verwaltete Verbindungen Beim Akzeptieren eines WebSockets können Sie einen Bezeichner angeben. Identifizierte Sockets werden in [HttpServer.WebSockets](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServer.WebSockets.md) registriert, wodurch der Server aktive Verbindungen außerhalb der Route, die sie akzeptiert hat, finden kann. ```cs router.MapGet("/connect/", async (HttpRequest req) => { string userId = req.RouteParameters["userId"].GetString(); using var ws = await req.GetWebSocketAsync(identifier: $"user:{userId}"); ws.State = userId; ws.PingPolicy.Start( dataMessage: "ping", interval: TimeSpan.FromSeconds(10)); while (await ws.ReceiveMessageAsync(TimeSpan.FromMinutes(5)) is { } message) { await ws.SendAsync("Received: " + message.GetString()); } return await ws.CloseAsync(); }); ``` Aus einem anderen Teil der Anwendung können Sie die Sammlung nach Bezeichner oder Prädikat abfragen: ```cs HttpWebSocket? socket = server.WebSockets.GetByIdentifier("user:42"); if (socket is { IsClosed: false }) { await socket.SendAsync("Your report is ready."); } foreach (HttpWebSocket activeSocket in server.WebSockets.Find(id => id.StartsWith("user:"))) { await activeSocket.SendAsync("Broadcast message"); } ``` Jeder `HttpWebSocket` stellt `Identifier`, `State`, `IsClosed` und `PingPolicy` bereit. Die Sammlung bietet außerdem `All()`, `Find(...)`, `GetByIdentifier(...)`, `ActiveConnections` und `DropAll()` für serververwaltete Verbindungsstrategien. --- # Discard-Syntax Source: https://docs.sisk-framework.org/de/docs/features/discard-syntax.html Der HTTP-Server kann verwendet werden, um auf eine Callback-Anfrage von einer Aktion, wie z.B. OAuth-Authentifizierung, zu hören und kann nach Erhalt dieser Anfrage verworfen werden. Dies kann in Fällen nützlich sein, in denen Sie eine Hintergrundaktion benötigen, aber keine gesamte HTTP-Anwendung dafür einrichten möchten. Das folgende Beispiel zeigt, wie ein lauschender HTTP-Server auf Port 5555 mit [CreateListener](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServer.CreateListener.md) erstellt und auf den nächsten Kontext gewartet wird: ```csharp using (var server = HttpServer.CreateListener(5555)) { // warte auf die nächste HTTP-Anfrage var context = await server.WaitNextAsync(); Console.WriteLine($"Angeforderter Pfad: {context.Request.Path}"); } ``` Die [WaitNext](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServer.WaitNext.md)-Funktion wartet auf den nächsten Kontext einer abgeschlossenen Anfrageverarbeitung. Sobald das Ergebnis dieser Operation erhalten wird, hat der Server die Anfrage bereits vollständig bearbeitet und die Antwort an den Client gesendet. [!TIP] wurde nicht übersetzt, da es nicht übersetzt werden sollte. Es fehlt jedoch im ursprünglichen Text. Es wurde angenommen, dass es nicht vorhanden ist. Es wurde nur der angeforderte Text übersetzt. --- # Abhängigkeitsinjektion Source: https://docs.sisk-framework.org/de/docs/features/instancing.html Es ist üblich, Mitglieder und Instanzen zu widmen, die für die gesamte Lebensdauer eines Anfrages bestehen bleiben, wie z.B. eine Datenbankverbindung, ein authentifizierter Benutzer oder ein Sitzungstoken. Eine der Möglichkeiten ist durch den [HttpContext.RequestBag](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpContext.md), der ein Dictionary erstellt, das für die gesamte Lebensdauer eines Anfrages besteht. Dieses Dictionary kann von [Anfragebehandlern](https://docs.sisk-framework.org/de/docs/fundamentals/request-handlers.md) zugreift werden und definiert Variablen während dieser Anfrage. Zum Beispiel setzt ein Anfragebehandler, der einen Benutzer authentifiziert, diesen Benutzer im `HttpContext.RequestBag` und innerhalb der Anfrage-Logik kann dieser Benutzer mit `HttpContext.RequestBag.Get()` abgerufen werden. Die im Dictionary definierten Objekte sind auf den Anfrage-Lebenszyklus beschränkt. Sie werden am Ende der Anfrage entsorgt. Das Senden einer Antwort definiert nicht unbedingt das Ende des Anfrage-Lebenszyklus. Wenn [Anfragebehandler](https://docs.sisk-framework.org/de/docs/fundamentals/request-handlers.md), die nach dem Senden einer Antwort ausgeführt werden, die `RequestBag`-Objekte noch existieren und noch nicht entsorgt wurden. Hier ist ein Beispiel: ```csharp {title="RequestHandlers/AuthenticateUser.cs"} public class AuthenticateUser : IRequestHandler { public RequestHandlerExecutionMode ExecutionMode { get; init; } = RequestHandlerExecutionMode.BeforeResponse; public HttpResponse? Execute(HttpRequest request, HttpContext context) { User authenticatedUser = AuthenticateUser(request); context.RequestBag.Set(authenticatedUser); return null; // advance to the next request handler or request logic } } ``` ```csharp {title="Controllers/HelloController.cs"} [RouteGet("/hello")] [RequestHandler] public HttpResponse SayHello(HttpRequest request) { var authenticatedUser = request.Bag.Get(); return new HttpResponse() { Content = new StringContent($"Hallo {authenticatedUser.Name}!") }; } ``` Dies ist ein vorläufiges Beispiel für diese Operation. Die Instanz von `User` wurde innerhalb des Anfragebehandlers für die Authentifizierung erstellt, und alle Routen, die diesen Anfragebehandler verwenden, haben die Garantie, dass es eine `User`-Instanz in ihrem `HttpContext.RequestBag` gibt. Es ist möglich, Logik zu definieren, um Instanzen zu erhalten, wenn sie nicht zuvor im `RequestBag` definiert wurden, durch Methoden wie [GetOrAdd](https://docs.sisk-framework.org/api/Sisk.Core.Entity.TypedValueDictionary.GetOrAdd.md) oder [GetOrAddAsync](https://docs.sisk-framework.org/api/Sisk.Core.Entity.TypedValueDictionary.GetOrAddAsync.md). Seit Version 1.3 wurde die statische Eigenschaft [HttpContext.Current](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpContext.Current.md) eingeführt, die den Zugriff auf den aktuellen `HttpContext` des Anfragekontexts ermöglicht. Dies ermöglicht es, Mitglieder des `HttpContext` außerhalb der aktuellen Anfrage zu exponieren und Instanzen in Route-Objekten zu definieren. Das folgende Beispiel definiert einen Controller, der Mitglieder enthält, die häufig vom Kontext einer Anfrage zugreift werden. ```csharp {title="Controllers/Controller.cs"} public abstract class Controller : RouterModule { // Erhalten Sie die bestehende oder erstellen Sie eine neue Datenbankinstanz für diese Anfrage protected DbContext Database => HttpContext.Current.RequestBag.GetOrAdd(() => new DbContext()); // Lazy-Loading von Repositories ist auch üblich protected IUserRepository Users => HttpContext.Current.RequestBag.GetOrAdd(() => new UserRepository(Database)); protected IBlogRepository Blogs => HttpContext.Current.RequestBag.GetOrAdd(() => new BlogRepository(Database)); protected IBlogPostRepository BlogPosts => HttpContext.Current.RequestBag.GetOrAdd(() => new BlogPostRepository(Database)); // Die folgende Zeile wird einen Fehler werfen, wenn die Eigenschaft aufgerufen wird, wenn der Benutzer nicht // im RequestBag definiert ist protected User AuthenticatedUser => => HttpContext.Current.RequestBag.Get(); // Exponieren des HttpRequest-Objekts wird auch unterstützt protected HttpRequest Request => HttpContext.Current.Request } ``` Und definieren Sie Typen, die von dem Controller erben: ```csharp {title="Controllers/PostsController.cs"} [RoutePrefix("/api/posts/{author}")] sealed class PostsController : Controller { protected Guid AuthorId => Request.RouteParameters["author"].GetInteger(); [RouteGet] public IAsyncEnumerable ListPosts() { return BlogPosts.GetPostsAsync(authorId: AuthorId); } [RouteGet("")] public async Task GetPost() { int postId = Request.RouteParameters["id"].GetInteger(); Post? post = await BlogPosts .FindPostAsync(post => post.Id == postId && post.AuthorId == AuthorId); return post; } } ``` Für das obige Beispiel müssen Sie einen [Wert-Handler](https://docs.sisk-framework.org/de/docs/fundamentals/responses.md#implicit-response-types) in Ihrem Router konfigurieren, damit die Objekte, die vom Router zurückgegeben werden, in eine gültige [HttpResponse](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpResponse.md) umgewandelt werden. Beachten Sie, dass die Methoden kein `HttpRequest request`-Argument haben, wie es in anderen Methoden der Fall ist. Dies liegt daran, dass der Router seit Version 1.3 zwei Arten von Delegaten für Routing-Antworten unterstützt: [RouteAction](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouteAction.md), der standardmäßige Delegate, der ein `HttpRequest`-Argument erhält, und [ParameterlessRouteAction](https://docs.sisk-framework.org/api/Sisk.Core.Routing.ParameterlessRouteAction.md). Das `HttpRequest`-Objekt kann immer noch durch beide Delegaten über die [Request](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpContext.Request.md)-Eigenschaft des statischen `HttpContext` auf dem Thread zugegriffen werden. Im obigen Beispiel haben wir ein entsorgbares Objekt, die `DbContext`, definiert, und wir müssen sicherstellen, dass alle im `DbContext` erstellten Instanzen entsorgt werden, wenn die HTTP-Sitzung endet. Dazu können wir zwei Methoden verwenden. Eine Möglichkeit besteht darin, einen [Anfragebehandler](https://docs.sisk-framework.org/de/docs/fundamentals/request-handlers.md) zu erstellen, der nach der Aktion des Routers ausgeführt wird, und die andere Möglichkeit besteht darin, einen benutzerdefinierten [Server-Handler](https://docs.sisk-framework.org/de/docs/advanced/http-server-handlers.md) zu verwenden. Für die erste Methode können wir den Anfragebehandler inline direkt im [OnSetup](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouterModule.OnSetup.md)-Methoden erben von `RouterModule` erstellen: ```csharp {title="Controllers/PostsController.cs"} public abstract class Controller : RouterModule { ... protected override void OnSetup(Router parentRouter) { base.OnSetup(parentRouter); HasRequestHandler(RequestHandler.Create( execute: (req, ctx) => { // Erhalten Sie eine im Anfragebehandler-Kontext definierte DbContext und // entsorgen Sie sie ctx.RequestBag.GetOrDefault()?.Dispose(); return null; }, executionMode: RequestHandlerExecutionMode.AfterResponse)); } } ``` > [!TIP] > > Seit Sisk-Version 1.4 ist die Eigenschaft [HttpServerConfiguration.DisposeDisposableContextValues](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.DisposeDisposableContextValues.md) eingeführt und standardmäßig aktiviert, die definiert, ob der HTTP-Server alle `IDisposable`-Werte im Kontext-Beutel entsorgen soll, wenn eine HTTP-Sitzung geschlossen wird. Die obige Methode stellt sicher, dass die `DbContext` entsorgt wird, wenn die HTTP-Sitzung abgeschlossen ist. Sie können dies für weitere Mitglieder tun, die am Ende einer Antwort entsorgt werden müssen. Für die zweite Methode können Sie einen benutzerdefinierten [Server-Handler](https://docs.sisk-framework.org/de/docs/advanced/http-server-handlers.md) erstellen, der die `DbContext` entsorgt, wenn die HTTP-Sitzung abgeschlossen ist. ```csharp {title="Server/Handlers/ObjectDisposerHandler.cs"} public class ObjectDisposerHandler : HttpServerHandler { protected override void OnHttpRequestClose(HttpServerExecutionResult result) { result.Context.RequestBag.GetOrDefault()?.Dispose(); } } ``` Und verwenden Sie es in Ihrem App-Builder: ```csharp {title="Program.cs"} using var host = HttpServer.CreateBuilder() .UseHandler() .Build(); ``` Dies ist eine Möglichkeit, Code-Reinigung zu handhaben und die Abhängigkeiten einer Anfrage getrennt von der Art des Moduls zu halten, das verwendet wird, um die Menge an dupliziertem Code innerhalb jeder Aktion eines Routers zu reduzieren. Es ist eine Praxis, die ähnlich ist wie die, die bei der Abhängigkeitsinjektion in Frameworks wie ASP.NET verwendet wird. --- # Streaming-Inhalt Source: https://docs.sisk-framework.org/de/docs/features/content-streaming.html Das Sisk unterstützt das Lesen und Senden von Inhalten als Streams an und von Clients. Diese Funktion ist nützlich, um den Speicherüberkopft für die Serialisierung und Deserialisierung von Inhalten während der Lebensdauer einer Anfrage zu reduzieren. ## Anfrage-Inhalt-Stream Kleine Inhalte werden automatisch in den HTTP-Verbindungspuffer-Speicher geladen, sodass dieser Inhalt schnell in [HttpRequest.Body](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Body.md) und [HttpRequest.RawBody](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.RawBody.md) geladen wird. Für größere Inhalte kann die [HttpRequest.GetRequestStream](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.GetRequestStream.md)-Methode verwendet werden, um den Anfrage-Inhalt-Lese-Stream zu erhalten. Es ist erwähnenswert, dass die [HttpRequest.GetMultipartFormContent](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.GetMultipartFormContent.md)-Methode den gesamten Anfrage-Inhalt in den Speicher lädt, sodass sie möglicherweise nicht für das Lesen großer Inhalte geeignet ist. Betrachten Sie das folgende Beispiel: ```csharp {title="Controller/UploadDocument.cs"} [RoutePost ( "/api/upload-document/" )] public async Task UploadDocument ( HttpRequest request ) { var fileName = request.RouteParameters [ "filename" ].GetString (); if (!request.HasContents) { // Anfrage enthält keinen Inhalt return new HttpResponse ( HttpStatusInformation.BadRequest ); } var contentStream = request.GetRequestStream (); var outputFileName = Path.Combine ( AppDomain.CurrentDomain.BaseDirectory, "uploads", fileName ); using (var fs = File.Create ( outputFileName )) { await contentStream.CopyToAsync ( fs ); } return new HttpResponse () { Content = JsonContent.Create ( new { message = "Datei erfolgreich gesendet." } ) }; } ``` Im obigen Beispiel liest die `UploadDocument`-Methode den Anfrage-Inhalt und speichert den Inhalt in einer Datei. Es wird keine zusätzliche Speicherzuweisung vorgenommen, außer für den Lese-Puffer, der von `Stream.CopyToAsync` verwendet wird. Das obige Beispiel reduziert den Druck der Speicherzuweisung für sehr große Dateien, was die Anwendungsleistung optimieren kann. Eine gute Praxis ist es, immer ein [CancellationToken](https://learn.microsoft.com/pt-br/dotnet/api/system.threading.cancellationtoken) in einer Operation zu verwenden, die zeitaufwändig sein kann, wie z. B. das Senden von Dateien, da es von der Netzwerkgeschwindigkeit zwischen Client und Server abhängt. Die Anpassung mit einem CancellationToken kann wie folgt vorgenommen werden: ```csharp {title="Controller/UploadDocument.cs"} // Der CancellationToken unten wird eine Ausnahme auslösen, wenn die 30-Sekunden-Frist erreicht ist. CancellationTokenSource copyCancellation = new CancellationTokenSource ( delay: TimeSpan.FromSeconds ( 30 ) ); try { using (var fs = File.Create ( outputFileName )) { await contentStream.CopyToAsync ( fs, copyCancellation.Token ); } } catch (OperationCanceledException) { return new HttpResponse ( HttpStatusInformation.BadRequest ) { Content = JsonContent.Create ( new { Error = "Der Upload hat die maximale Upload-Zeit (30 Sekunden) überschritten." } ) }; } ``` ## Antwort-Inhalt-Stream Das Senden von Antwort-Inhalten ist auch möglich. Derzeit gibt es zwei Möglichkeiten, dies zu tun: über die [HttpRequest.GetResponseStream](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.GetResponseStream.md)-Methode und mit einem Inhalt vom Typ [StreamContent](https://learn.microsoft.com/pt-br/dotnet/api/system.net.http.streamcontent?view=net-9.0). Betrachten Sie ein Szenario, in dem wir eine Bilddatei bereitstellen müssen. Dazu können wir den folgenden Code verwenden: ```csharp {title="Controller/ImageController.cs"} [RouteGet ( "/api/profile-picture" )] public async Task UploadDocument ( HttpRequest request ) { // Beispiel-Methode, um ein Profilbild zu erhalten var profilePictureFilename = "profile-picture.jpg"; byte[] profilePicture = await File.ReadAllBytesAsync ( profilePictureFilename ); return new HttpResponse () { Content = new ByteArrayContent ( profilePicture ), Headers = new () { ContentType = "image/jpeg", ContentDisposition = $"inline; filename={profilePictureFilename}" } }; } ``` Die obige Methode führt eine Speicherzuweisung durch, wenn sie den Bildinhalt liest. Wenn das Bild groß ist, kann dies ein Leistungsproblem verursachen und in Spitzenzeiten sogar einen Speicherüberlauf und einen Serverabsturz verursachen. In diesen Situationen kann Zwischenspeicherung nützlich sein, aber sie wird das Problem nicht eliminieren, da der Speicher immer noch für diese Datei reserviert ist. Zwischenspeicherung kann den Druck der Speicherzuweisung für jede Anfrage lindern, aber für große Dateien wird sie nicht ausreichen. Das Senden des Bildes über einen Stream kann eine Lösung für das Problem sein. Anstatt den gesamten Bildinhalt zu lesen, wird ein Lese-Stream auf der Datei erstellt und mit einem kleinen Puffer an den Client kopiert. #### Senden über die GetResponseStream-Methode Die [HttpRequest.GetResponseStream](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.GetResponseStream.md)-Methode erstellt ein Objekt, das das Senden von Teilen der HTTP-Antwort ermöglicht, während der Inhalt-Fluss vorbereitet wird. Diese Methode ist manuell und erfordert, dass Sie den Status, die Header und die Inhaltsgröße vor dem Senden des Inhalts definieren. ```csharp {title="Controller/ImageController.cs"} [RouteGet ( "/api/profile-picture" )] public async Task UploadDocument ( HttpRequest request ) { var profilePictureFilename = "profile-picture.jpg"; // in dieser Form des Sendens müssen der Status und der Header definiert werden // bevor der Inhalt gesendet wird var requestStreamManager = request.GetResponseStream (); requestStreamManager.SetStatus ( System.Net.HttpStatusCode.OK ); requestStreamManager.SetHeader ( HttpKnownHeaderNames.ContentType, "image/jpeg" ); requestStreamManager.SetHeader ( HttpKnownHeaderNames.ContentDisposition, $"inline; filename={profilePictureFilename}" ); using (var fs = File.OpenRead ( profilePictureFilename )) { // in dieser Form des Sendens muss auch die Inhaltsgröße definiert werden // bevor sie gesendet wird. requestStreamManager.SetContentLength ( fs.Length ); // wenn Sie die Inhaltsgröße nicht kennen, können Sie chunked-encoding // verwenden, um den Inhalt zu senden requestStreamManager.SendChunked = true; // und dann schreiben Sie in den Ausgabestream await fs.CopyToAsync ( requestStreamManager.ResponseStream ); } } ``` #### Senden von Inhalten über einen StreamContent Die [StreamContent](https://learn.microsoft.com/pt-br/dotnet/api/system.net.http.streamcontent?view=net-9.0)-Klasse ermöglicht das Senden von Inhalten aus einer Datenquelle als Byte-Stream. Diese Form des Sendens ist einfacher und entfernt die vorherigen Anforderungen und ermöglicht sogar die Verwendung von [Komprimierungs-Codierung](https://docs.sisk-framework.org/de/docs/fundamentals/responses.md#gzip-deflate-and-brotli-compression), um die Inhaltsgröße zu reduzieren. ```csharp {title="Controller/ImageController.cs"} [RouteGet ( "/api/profile-picture" )] public HttpResponse UploadDocument ( HttpRequest request ) { var profilePictureFilename = "profile-picture.jpg"; return new HttpResponse () { Content = new StreamContent ( File.OpenRead ( profilePictureFilename ) ), Headers = new () { ContentType = "image/jpeg", ContentDisposition = $"inline; filename=\"{profilePictureFilename}\"" } }; } ``` > [!IMPORTANT] > > Bei dieser Art von Inhalt sollten Sie den Stream nicht in einem `using`-Block einwickeln. Der Inhalt wird automatisch durch den HTTP-Server verworfen, wenn der Inhalts-Fluss abgeschlossen ist, mit oder ohne Fehler. --- # Aktivierung von CORS (Cross-Origin Resource Sharing) in Sisk Source: https://docs.sisk-framework.org/de/docs/features/cors.html Sisk verfügt über ein Tool, das bei der Verarbeitung von [Cross-Origin Resource Sharing (CORS)](https://developer.mozilla.org/en-US/docs/de/Web/HTTP/Guides/CORS) nützlich sein kann, wenn Sie Ihren Dienst öffentlich zugänglich machen. Diese Funktion ist nicht Teil des HTTP-Protokolls, sondern eine spezifische Funktion von Webbrowsern, die von der W3C definiert wird. Dieser Sicherheitsmechanismus verhindert, dass eine Webseite Anfragen an einen anderen Domain als diejenige sendet, die die Webseite bereitgestellt hat. Ein Dienstanbieter kann bestimmten Domains den Zugriff auf seine Ressourcen erlauben oder nur einer. ## Same Origin Damit eine Ressource als "same origin" identifiziert wird, muss eine Anfrage den [Origin](https://developer.mozilla.org/en-US/docs/de/Web/HTTP/Reference/Headers/Origin)-Header in ihrer Anfrage enthalten: ```http GET /api/users HTTP/1.1 Host: example.com Origin: http://example.com ... ``` Und der Remote-Server muss mit einem [Access-Control-Allow-Origin](https://developer.mozilla.org/en-US/docs/de/Web/HTTP/Headers/Access-Control-Allow-Origin)-Header antworten, der den gleichen Wert wie die angeforderte Ursprung hat: ```http HTTP/1.1 200 OK Access-Control-Allow-Origin: http://example.com ... ``` Diese Überprüfung ist **explizit**: Der Host, Port und Protokoll müssen identisch mit dem Angeforderten sein. Überprüfen Sie das Beispiel: - Ein Server antwortet, dass sein `Access-Control-Allow-Origin` `https://example.com` ist: - `https://example.net` - die Domäne ist unterschiedlich. - `http://example.com` - das Schema ist unterschiedlich. - `http://example.com:5555` - der Port ist unterschiedlich. - `https://www.example.com` - der Host ist unterschiedlich. In der Spezifikation ist nur die Syntax für beide Header zulässig, sowohl für Anfragen als auch für Antworten. Der URL-Pfad wird ignoriert. Der Port wird auch weggelassen, wenn es sich um einen Standardport (80 für HTTP und 443 für HTTPS) handelt. ```http Origin: null Origin: :// Origin: ://: ``` ## Aktivierung von CORS Natürlich haben Sie das [CrossOriginResourceSharingHeaders](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.md)-Objekt innerhalb Ihres [ListeningHost](https://docs.sisk-framework.org/api/Sisk.Core.Http.ListeningHost.md). Sie können CORS beim Initialisieren des Servers konfigurieren: ```csharp static async Task Main(string[] args) { using var app = HttpServer.CreateBuilder() .UseCors(new CrossOriginResourceSharingHeaders( allowOrigin: "http://example.com", allowHeaders: ["Authorization"], exposeHeaders: ["Content-Type"])) .Build(); await app.StartAsync(); } ``` Der obige Code sendet die folgenden Header für **alle Antworten**: ```http HTTP/1.1 200 OK Access-Control-Allow-Origin: http://example.com Access-Control-Allow-Headers: Authorization Access-Control-Expose-Headers: Content-Type ``` Diese Header müssen für alle Antworten an einen Web-Client gesendet werden, einschließlich Fehler und Umleitungen. Sie können feststellen, dass die [CrossOriginResourceSharingHeaders](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.md)-Klasse zwei ähnliche Eigenschaften hat: [AllowOrigin](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AllowOrigin.md) und [AllowOrigins](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AllowOrigins.md). Beachten Sie, dass eine plural und die andere singular ist. - Die **AllowOrigin**-Eigenschaft ist statisch: Nur die Ursprung, die Sie angeben, wird für alle Antworten gesendet. - Die **AllowOrigins**-Eigenschaft ist dynamisch: Der Server überprüft, ob die Ursprung der Anfrage in dieser Liste enthalten ist. Wenn sie gefunden wird, wird sie für die Antwort dieser Ursprung gesendet. ### Wildcards und automatische Header Alternativ können Sie ein Wildcard-Zeichen (`*`) in der Antwort-Ursprung verwenden, um anzugeben, dass jede Ursprung auf die Ressource zugreifen darf. Allerdings ist dieser Wert nicht zulässig für Anfragen, die Anmeldeinformationen (Autorisierungsheader) enthalten, und dieser Vorgang [wird zu einem Fehler führen](https://developer.mozilla.org/en-US/docs/de/Web/HTTP/Guides/CORS/Errors/CORSNotSupportingCredentials). Sie können dieses Problem umgehen, indem Sie explizit auflisten, welche Ursprünge über die [AllowOrigins](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AllowOrigins.md)-Eigenschaft zugelassen werden oder auch die [AutoAllowOrigin](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AutoAllowOrigin.md)-Konstante im Wert von [AllowOrigin](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AllowOrigin.md) verwenden. Diese magische Eigenschaft wird den `Access-Control-Allow-Origin`-Header für den gleichen Wert wie den `Origin`-Header der Anfrage definieren. Sie können auch [AutoFromRequestMethod](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AutoFromRequestMethod.md) und [AutoFromRequestHeaders](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AutoFromRequestHeaders.md) für ein ähnliches Verhalten wie `AllowOrigin` verwenden, das automatisch basierend auf den gesendeten Headern antwortet. ```csharp using var host = HttpServer.CreateBuilder() .UseCors(new CrossOriginResourceSharingHeaders( // Antworte basierend auf dem Origin-Header der Anfrage allowOrigin: CrossOriginResourceSharingHeaders.AutoAllowOrigin, // Antworte basierend auf dem Access-Control-Request-Method-Header oder der Anfragemethode allowMethods: [CrossOriginResourceSharingHeaders.AutoFromRequestMethod], // Antworte basierend auf dem Access-Control-Request-Headers-Header oder den gesendeten Headern allowHeaders: [CrossOriginResourceSharingHeaders.AutoFromRequestHeaders], exposeHeaders: [HttpKnownHeaderNames.ContentType, "X-Authenticated-Account-Id"], allowCredentials: true)) .Build(); ``` ## Andere Möglichkeiten, CORS anzuwenden Wenn Sie mit [Dienstanbietern](https://docs.sisk-framework.org/de/docs/extensions/service-providers.md) arbeiten, können Sie Werte überschreiben, die in der Konfigurationsdatei definiert sind: ```csharp static async Task Main(string[] args) { using var app = HttpServer.CreateBuilder() .UsePortableConfiguration(...) .UseCors(cors => { // Überschreibt die Ursprung, die in der Konfigurationsdatei definiert ist. cors.AllowOrigin = "http://example.com"; }) .Build(); await app.StartAsync(); } ``` ## Deaktivierung von CORS auf bestimmten Routen Die `UseCors`-Eigenschaft ist für beide Routen und alle Routenattribute verfügbar und kann mit dem folgenden Beispiel deaktiviert werden: ```csharp [RoutePrefix("api/widgets")] public class WidgetController : Controller { // GET /api/widgets/colors [RouteGet("/colors", UseCors = false)] public IEnumerable GetWidgets() { return new[] { "Grünes Widget", "Rotes Widget" }; } } ``` ## Ersetzen von Werten in der Antwort Sie können Werte explizit in einer Router-Aktion ersetzen oder entfernen: ```csharp [RoutePrefix("api/widgets")] public class WidgetController : Controller { public IEnumerable GetWidgets(HttpRequest request) { // Entfernt den Access-Control-Allow-Credentials-Header request.Context.OverrideHeaders.AccessControlAllowCredentials = string.Empty; // Ersetzt den Access-Control-Allow-Origin request.Context.OverrideHeaders.AccessControlAllowOrigin = "https://contorso.com"; return new[] { "Grünes Widget", "Rotes Widget" }; } } ``` ## Preflight-Anfragen Eine Preflight-Anfrage ist eine [OPTIONS](https://developer.mozilla.org/en-US/docs/de/Web/HTTP/Reference/Methods/OPTIONS)-Methode-Anfrage, die der Client vor der eigentlichen Anfrage sendet. Der Sisk-Server antwortet immer auf die Anfrage mit einem `200 OK` und den anwendbaren CORS-Headern, und dann kann der Client mit der eigentlichen Anfrage fortfahren. Diese Bedingung wird nur nicht angewendet, wenn eine Route für die Anfrage mit der [RouteMethod](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouteMethod.md) explizit für `Options` konfiguriert ist. ## Deaktivierung von CORS global Es ist nicht möglich, dies zu tun. Um CORS nicht zu verwenden, konfigurieren Sie es nicht. --- # Dateiserver Source: https://docs.sisk-framework.org/de/docs/features/file-server.html Sisk stellt den Namespace `Sisk.Http.FileSystem` bereit, der Werkzeuge zum Bereitstellen statischer Dateien, zur Verzeichnisauflistung und zur Dateikonvertierung enthält. Diese Funktion ermöglicht das Bereitstellen von Dateien aus einem lokalen Verzeichnis, mit Unterstützung für Bereichsanfragen (Audio-/Video-Streaming) und benutzerdefinierte Dateiverarbeitung. ## Bereitstellen statischer Dateien Der einfachste Weg, statische Dateien bereitzustellen, ist [Router.MapFileSystem](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.MapFileSystem.md). Diese Methode ordnet ein URL-Präfix einem Verzeichnis auf dem Datenträger zu. ```cs using Sisk.Core.Http; using Sisk.Core.Http.FileSystem; // ordnet die Wurzel des Servers dem aktuellen Verzeichnis zu mainRouter.MapFileSystem("/", Directory.GetCurrentDirectory()); // ordnet /assets dem Ordner "public/assets" zu mainRouter.MapFileSystem( "/assets", Path.Combine(Directory.GetCurrentDirectory(), "public", "assets")); ``` Wenn eine Anfrage dem Routen-Präfix entspricht, sucht der `HttpFileServerHandler` nach einer Datei im angegebenen Verzeichnis. Wird sie gefunden, wird die Datei bereitgestellt; andernfalls wird eine 404-Antwort zurückgegeben (oder 403, wenn der Zugriff verweigert wird). `HttpFileServer.CreateServingRoute` ist weiterhin verfügbar, wenn Sie ein `Route`-Objekt explizit erstellen müssen, aber `MapFileSystem` ist die direkteste Option für Anwendungscode. ## HttpFileServerHandler Für mehr Kontrolle darüber, wie Dateien bereitgestellt werden, können Sie `HttpFileServerHandler` manuell instanziieren und konfigurieren. ```cs var fileHandler = new HttpFileServerHandler("/var/www/html"); // aktiviert die Verzeichnisauflistung (standardmäßig deaktiviert) fileHandler.AllowDirectoryListing = true; // legt ein benutzerdefiniertes Routen-Präfix fest (dies wird vom Anforderungspfad entfernt) fileHandler.RoutePrefix = "/public"; // registriert den Handler unter /public mainRouter.MapFileSystem("/public", fileHandler); ``` ### Konfiguration | Eigenschaft | Beschreibung | |---|---| | `RootDirectoryPath` | Der absolute oder relative Pfad zum Stammverzeichnis, aus dem Dateien bereitgestellt werden. | | `RoutePrefix` | Das Routen-Präfix, das beim Auflösen von Dateien vom Anforderungspfad entfernt wird. Standard ist `/`. | | `AllowDirectoryListing` | Wenn auf `true` gesetzt, aktiviert die Verzeichnisauflistung, wenn ein Verzeichnis angefordert wird und keine Indexdatei gefunden wird. Standard ist `false`. | | `FileConverters` | Eine Liste von `HttpFileServerFileConverter`, die verwendet werden, um Dateien vor dem Bereitstellen zu transformieren. | ## Verzeichnisauflistung Wenn `AllowDirectoryListing` aktiviert ist und der Benutzer einen Verzeichnispfad anfordert, erzeugt Sisk eine HTML-Seite, die den Inhalt dieses Verzeichnisses auflistet. Die Verzeichnisauflistung enthält: - Navigation zum übergeordneten Verzeichnis (`..`). - Liste der Unterverzeichnisse. - Liste der Dateien mit Größe und letztem Änderungsdatum. ## Dateikonverter Dateikonverter ermöglichen es Ihnen, bestimmte Dateitypen abzufangen und anders zu verarbeiten. Beispielsweise könnten Sie ein Bild transkodieren, eine Datei on-the-fly komprimieren oder eine Datei mit Teilinhalt (Range-Anfragen) bereitstellen. Sisk enthält zwei integrierte Konverter für Media-Streaming: - `HttpFileAudioConverter`: Unterstützt `.mp3`, `.ogg`, `.wav`, `.flac`, `.ogv`. - `HttpFileVideoConverter`: Unterstützt `.webm`, `.avi`, `.mkv`, `.mpg`, `.mpeg`, `.wmv`, `.mov`, `.mp4`. Diese Konverter ermöglichen die Unterstützung von **HTTP Range Requests**, sodass Clients in Audio- und Videodateien vorspulen können. ### Erstellen eines benutzerdefinierten Konverters Um einen benutzerdefinierten Dateikonverter zu erstellen, erben Sie von `HttpFileServerFileConverter` und implementieren `CanConvert` und `Convert`. ```cs using Sisk.Core.Http; using Sisk.Core.Http.FileSystem; public class MyTextConverter : HttpFileServerFileConverter { public override bool CanConvert(FileInfo file) { // nur auf .txt-Dateien anwenden return file.Extension.Equals(".txt", StringComparison.OrdinalIgnoreCase); } public override HttpResponse Convert(FileInfo file, HttpRequest request) { string content = File.ReadAllText(file.FullName); // gesamten Textinhalt in Großbuchstaben umwandeln return new HttpResponse(200) { Content = new StringContent(content.ToUpper()) }; } } ``` Dann fügen Sie ihn zu Ihrem Handler hinzu: ```cs var handler = new HttpFileServerHandler("./files"); handler.FileConverters.Add(new MyTextConverter()); ``` --- # Modellkontextprotokoll Source: https://docs.sisk-framework.org/de/docs/extensions/mcp.html Es ist möglich, Anwendungen zu erstellen, die Agentenmodellen Kontext bereitstellen, indem große Sprachmodelle (LLMs) verwendet werden, mithilfe des Pakets [Sisk.ModelContextProtocol](https://www.nuget.org/packages/Sisk.ModelContextProtocol/) : ```bash dotnet add package Sisk.ModelContextProtocol ``` Dieses Paket stellt nützliche Klassen und Methoden zum Erstellen von MCP‑Servern bereit, die über [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#streamable-http) funktionieren. Die aktuelle Implementierung unterstützt Werkzeuge über die Protokollversion `2025-06-18`. > [!NOTE] > > Bevor Sie beginnen, beachten Sie, dass sich dieses Paket noch in der Entwicklung befindet und Verhaltensweisen aufweisen kann, die nicht der Spezifikation entsprechen. Lesen Sie die [Paketdetails](https://github.com/sisk-http/core/tree/main/extensions/Sisk.ModelContextProtocol), um zu erfahren, was sich noch in Entwicklung befindet und was noch nicht funktioniert. ## Erste Schritte mit MCP Die Klasse [McpProvider](https://docs.sisk-framework.org/api/Sisk.ModelContextProtocol.McpProvider.md) ist der Einstiegspunkt zum Definieren eines MCP‑Servers. Es handelt sich um ein versiegeltes Provider‑Objekt, das beim Start konfiguriert werden kann. Ihre Sisk‑Anwendung kann einen oder mehrere MCP‑Provider besitzen. ```csharp McpProvider mcp = new McpProvider( serverName: "math-server", serverTitle: "Mathematics server", serverVersion: new Version(1, 0)); mcp.Tools.Add(new McpTool( name: "math_sum", description: "Sums one or more numbers.", schema: JsonSchema.CreateObjectSchema( properties: new Dictionary() { { "numbers", JsonSchema.CreateArraySchema( itemsSchema: JsonSchema.CreateNumberSchema(), minItems: 1, description: "The numbers to sum.") } }, requiredProperties: ["numbers"]), executionHandler: async (McpToolContext context) => { var numbers = context.Arguments["numbers"].GetJsonArray().ToArray(); var sum = numbers.Sum(); return await Task.FromResult(McpToolResult.CreateText($"Sum result: {sum:N4}")); })); ``` Wenn Ihre Anwendung nur einen MCP‑Provider bereitstellt, können Sie das Singleton des Builders verwenden: ```csharp static void Main(string[] args) { using var host = HttpServer.CreateBuilder() .UseMcp(mcp => { mcp.ServerName = "math-server"; mcp.ServerTitle = "Mathematics server"; mcp.Tools.Add(new McpTool( name: "math_sum", description: "Sums one or more numbers.", schema: JsonSchema.CreateObjectSchema( properties: new Dictionary() { { "numbers", JsonSchema.CreateArraySchema( itemsSchema: JsonSchema.CreateNumberSchema(), minItems: 1, description: "The numbers to sum.") } }, requiredProperties: ["numbers"]), executionHandler: async (McpToolContext context) => { var numbers = context.Arguments["numbers"].GetJsonArray().ToArray(); var sum = numbers.Sum(); return await Task.FromResult(McpToolResult.CreateText($"Sum result: {sum:N4}")); })); }) .UseRouter(router => { router.MapAny("/mcp", async (HttpRequest req) => { return await req.HandleMcpRequestAsync(); }); }) .Build(); host.Start(); } ``` Der Endpunkt muss sowohl `GET`‑ als auch `POST`‑Anfragen akzeptieren, daher ist `MapAny` die einfachste Routen‑Zuordnung. `HandleMcpRequestAsync` gibt ein [HttpResponse](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpResponse.md) zurück, und Ihre Route muss dieses zurückgeben. Wenn Sie mehrere Provider in einer Anwendung benötigen, verzichten Sie auf das Singleton und rufen Sie [McpProvider.HandleRequestAsync](https://docs.sisk-framework.org/api/Sisk.ModelContextProtocol.McpProvider.HandleRequestAsync.md) direkt aus jeder Route auf: ```csharp var mathProvider = new McpProvider("math-server", "Mathematics server", new Version(1, 0)); router.MapAny("/mcp/math", async request => { return await mathProvider.HandleRequestAsync(request); }); ``` ## Erstellen von JSON‑Schemas für Funktionen Die Bibliothek [Sisk.ModelContextProtocol] verwendet einen Fork von [LightJson](https://github.com/CypherPotato/LightJson) für die Manipulation von JSON und JSON‑Schemas. Diese Implementierung bietet einen fluenten JSON‑Schema‑Builder für verschiedene Objekte: - JsonSchema.CreateObjectSchema - JsonSchema.CreateArraySchema - JsonSchema.CreateBooleanSchema - JsonSchema.CreateNumberSchema - JsonSchema.CreateStringSchema - JsonSchema.Empty Beispiel: ```csharp JsonSchema.CreateObjectSchema( properties: new Dictionary() { { "numbers", JsonSchema.CreateArraySchema( itemsSchema: JsonSchema.CreateNumberSchema(), minItems: 1, description: "The numbers to sum.") } }, requiredProperties: ["numbers"]); ``` Erzeugt das folgende Schema: ```json { "type": "object", "properties": { "numbers": { "type": "array", "items": { "type": "number" }, "minItems": 1, "description": "The numbers to sum." } }, "required": ["numbers"] } ``` ## Behandlung von Funktionsaufrufen Die im Parameter `executionHandler` von [McpTool](https://docs.sisk-framework.org/api/Sisk.ModelContextProtocol.McpTool.md) definierte Funktion liefert ein JsonObject, das die Aufrufargumente enthält und fluently gelesen werden kann: ```csharp mcp.Tools.Add(new McpTool( name: "browser_do_action", description: "Run an browser action, such as scrolling, refreshing or navigating.", schema: JsonSchema.CreateObjectSchema( properties: new Dictionary() { { "action_name", JsonSchema.CreateStringSchema( enums: ["go_back", "refresh", "scroll_bottom", "scroll_top"], description: "The action name.") }, { "action_data", JsonSchema.CreateStringSchema( description: "Action parameter." ) } }, requiredProperties: ["action_name"]), executionHandler: async (McpToolContext context) => { // Lese den Aktionsnamen. Wirft eine Ausnahme, wenn null oder kein expliziter String string actionName = context.Arguments["action_name"].GetString(); // action_data ist nicht zwingend erforderlich, daher kann es hier null sein string? actionData = context.Arguments["action_data"].MaybeNull()?.GetString(); // Verarbeite die Browser‑Aktion basierend auf dem Aktionsnamen return await Task.FromResult( McpToolResult.CreateText($"Performed browser action: {actionName}")); })); ``` Werkzeug‑Argumente werden vor dem Aufruf des Handlers anhand des Schemas validiert. Bei einem Validierungsfehler gibt der Provider ein Fehl­ergebnis an den MCP‑Client zurück und ruft den Werkzeug‑Handler nicht auf. ## Funktions‑Ergebnisse Das Objekt [McpToolResult](https://docs.sisk-framework.org/api/Sisk.ModelContextProtocol.McpToolResult.md) bietet drei Methoden zum Erstellen von Inhalten für eine Werkzeug‑Antwort: - [CreateAudio(ReadOnlySpan, string)](https://docs.sisk-framework.org/api/Sisk.ModelContextProtocol.McpToolResult.CreateAudio.md): erstellt eine audio‑basierte Antwort für den MCP‑Client. - [CreateImage(ReadOnlySpan, string)](https://docs.sisk-framework.org/api/Sisk.ModelContextProtocol.McpToolResult.CreateImage.md): erstellt eine bild‑basierte Antwort für den MCP‑Client. - [CreateText(string)](https://docs.sisk-framework.org/api/Sisk.ModelContextProtocol.McpToolResult.CreateText.md): erstellt eine text‑basierte Antwort (Standard) für den MCP‑Client. Zusätzlich ist es möglich, mehrere unterschiedliche Inhalte zu einer einzigen JSON‑Werkzeug‑Antwort zu kombinieren: ```csharp mcp.Tools.Add(new McpTool( ... executionHandler: async (McpToolContext context) => { // simuliert reale Arbeit byte[] browserScreenshot = await browser.ScreenshotAsync(); return McpToolResult.Combine( McpToolResult.CreateText("Heres the screenshot of the browser:"), McpToolResult.CreateImage(browserScreenshot, "image/png") ) })); ``` Der Provider verarbeitet derzeit die Initialisierung, `tools/list`, `tools/call`, `ping` und `notifications/*`. Nicht unterstützte JSON‑RPC‑Methoden geben eine JSON‑RPC‑Fehlerantwort zurück. ## Fortlaufende Arbeit Das Modellkontextprotokoll ist ein Kommunikationsprotokoll für Agentenmodelle und Anwendungen, die Inhalte für diese bereitstellen. Es ist ein neues Protokoll, sodass seine Spezifikation häufig mit Deprecations, neuen Features und Breaking Changes aktualisiert wird. Es ist entscheidend, die Probleme zu verstehen, die das [Model Context Protocol](https://modelcontextprotocol.io/docs/de/getting-started/intro) löst, bevor Sie mit dem Bau von Agenten‑Anwendungen beginnen. Lesen Sie außerdem die Spezifikation des [Sisk.ModelContextProtocol](https://github.com/sisk-http/core/tree/main/extensions/Sisk.ModelContextProtocol) Pakets, um dessen Fortschritt, Status und mögliche Anwendungsfälle zu verstehen. --- # JSON-RPC-Erweiterung Source: https://docs.sisk-framework.org/de/docs/extensions/json-rpc.html Sisk verfügt über ein experimentelles Modul für eine [JSON-RPC 2.0](https://www.jsonrpc.org/specification) API, das es Ihnen ermöglicht, noch einfachere Anwendungen zu erstellen. Diese Erweiterung implementiert strikt die JSON-RPC 2.0 Transport‑Schnittstelle und bietet Transport über HTTP‑GET-, POST‑Anfragen sowie Web‑Sockets mit Sisk an. Sie können die Erweiterung über NuGet mit dem untenstehenden Befehl installieren. Beachten Sie, dass Sie in experimentellen/Beta‑Versionen die Option aktivieren sollten, nach Vorab‑Release‑Paketen in Visual Studio zu suchen. ```bash dotnet add package Sisk.JsonRpc ``` ## Transport‑Schnittstelle JSON‑RPC ist ein zustandsloses, asynchrones Remote‑Procedure‑Call‑(RPC‑)Protokoll, das JSON für die Datenkommunikation verwendet. Eine JSON‑RPC‑Anfrage wird typischerweise durch eine ID identifiziert, und eine Antwort wird mit derselben ID zurückgeliefert, die in der Anfrage gesendet wurde. Nicht alle Anfragen erfordern eine Antwort; solche werden „Benachrichtigungen“ genannt. Die [JSON‑RPC‑2.0‑Spezifikation](https://www.jsonrpc.org/specification) erklärt im Detail, wie der Transport funktioniert. Dieser Transport ist unabhängig davon, wo er eingesetzt wird. Sisk implementiert dieses Protokoll über HTTP und folgt den Vorgaben von [JSON‑RPC über HTTP](https://www.jsonrpc.org/historical/json-rpc-over-http.html), die GET‑Anfragen teilweise unterstützen, POST‑Anfragen jedoch vollständig. Web‑Sockets werden ebenfalls unterstützt und ermöglichen asynchrone Nachrichtenkommunikation. Eine JSON‑RPC‑Anfrage sieht ähnlich aus wie: ```json { "jsonrpc": "2.0", "method": "Sum", "params": [1, 2, 4], "id": 1 } ``` Und eine erfolgreiche Antwort sieht ähnlich aus wie: ```json { "jsonrpc": "2.0", "result": 7, "id": 1 } ``` ## JSON-RPC‑Methoden Das folgende Beispiel zeigt, wie man mit Sisk eine JSON‑RPC‑API erstellt. Eine Klasse für mathematische Operationen führt die Remote‑Operationen aus und liefert die serialisierte Antwort an den Client. ```csharp {title="Program.cs"} using var app = HttpServer.CreateBuilder(port: 5555) .UseJsonRPC((sender, args) => { // fügt alle mit WebMethod markierten Methoden dem JSON‑RPC‑Handler hinzu args.Handler.Methods.AddMethodsFromType(new MathOperations()); // mappt die /service‑Route, um JSON‑RPC‑POST‑ und GET‑Anfragen zu bearbeiten args.Router.MapPost("/service", args.Handler.Transport.HttpPost); args.Router.MapGet("/service", args.Handler.Transport.HttpGet); // mappt den JSON‑RPC‑WebSocket‑Transport auf GET /ws args.Router.MapGet("/ws", args.Handler.Transport.WebSocket); }) .Build(); await app.StartAsync(); ``` ```csharp {title="MathOperations.cs"} public class MathOperations { [WebMethod] public float Sum(float a, float b) { return a + b; } [WebMethod] public double Sqrt(float a) { return Math.Sqrt(a); } } ``` Das obige Beispiel mappt die Methoden `Sum` und `Sqrt` zum JSON‑RPC‑Handler, und diese Methoden stehen unter `GET /service`, `POST /service` und `GET /ws` zur Verfügung. Methodennamen sind nicht case‑sensitiv. Methodenparameter werden automatisch in ihre jeweiligen Typen deserialisiert. Die Verwendung einer Anfrage mit benannten Parametern wird ebenfalls unterstützt. Die JSON‑Serialisierung erfolgt durch die Bibliothek [LightJson](https://github.com/CypherPotato/LightJson). Wenn ein Typ nicht korrekt deserialisiert wird, können Sie einen spezifischen [JSON‑Konverter](https://github.com/CypherPotato/LightJson?tab=readme-ov-file#json-converters) für diesen Typ erstellen und ihn mit [JsonRpcHandler.JsonSerializerOptions](https://docs.sisk-framework.org/api/Sisk.JsonRPC.JsonRpcHandler.JsonSerializerOptions.md) verknüpfen. Sie können das rohe Objekt `$.params` aus der JSON‑RPC‑Anfrage auch direkt in Ihrer Methode erhalten. ```csharp {title="MathOperations.cs"} [WebMethod] public float Sum(JsonArray|JsonObject @params) { ... } ``` Damit dies funktioniert, muss `@params` der **einzige** Parameter Ihrer Methode sein und exakt den Namen `params` tragen (in C# ist das `@` notwendig, um diesen Parameternamen zu escapen). Die Deserialisierung von Parametern erfolgt sowohl für benannte Objekte als auch für positionsbasierte Arrays. Zum Beispiel kann die folgende Methode aus der Ferne mit beiden Anfragen aufgerufen werden: ```csharp [WebMethod] public float AddUserToStore(string apiKey, User user, UserStore store) { ... } ``` Bei einem Array muss die Reihenfolge der Parameter eingehalten werden. ```json { "jsonrpc": "2.0", "method": "AddUserToStore", "params": [ "1234567890", { "name": "John Doe", "email": "john@example.com" }, { "name": "My Store" } ], "id": 1 } ``` ## Anpassen des Serialisierers Sie können den JSON‑Serializer in der Eigenschaft [JsonRpcHandler.JsonSerializerOptions](https://docs.sisk-framework.org/api/Sisk.JsonRPC.JsonRpcHandler.JsonSerializerOptions.md) anpassen. In dieser Eigenschaft können Sie die Verwendung von [JSON5](https://json5.org/) zum Deserialisieren von Nachrichten aktivieren. Obwohl es keine Konformität zu JSON‑RPC 2.0 darstellt, ist JSON5 eine Erweiterung von JSON, die eine menschenlesbarere und klarere Schreibweise ermöglicht. ```csharp {title="Program.cs"} using var host = HttpServer.CreateBuilder ( 5556 ) .UseJsonRPC ( ( o, e ) => { // verwendet einen bereinigten Namensvergleich. Dieser Vergleich vergleicht nur Buchstaben // und Ziffern in einem Namen und verwirft andere Symbole. Beispiel: // foo_bar10 == FooBar10 e.Handler.JsonSerializerOptions.PropertyNameComparer = new JsonSanitizedComparer (); // aktiviert JSON5 für den JSON‑Interpreter. Auch wenn dies aktiviert ist, bleibt reines JSON weiterhin erlaubt e.Handler.JsonSerializerOptions.SerializationFlags = LightJson.Serialization.JsonSerializationFlags.Json5; // mappt die POST‑/service‑Route zum JSON‑RPC‑Handler e.Router.MapPost ( "/service", e.Handler.Transport.HttpPost ); } ) .Build (); host.Start (); ``` --- # SSL-Proxy Source: https://docs.sisk-framework.org/de/docs/extensions/ssl-proxy.html > [!WARNING] > Diese Funktion ist experimentell und sollte nicht in der Produktion verwendet werden. Bitte beachten Sie [dieses Dokument](https://docs.sisk-framework.org/de/docs/deploying.md#proxying-your-application), wenn Sie Sisk mit SSL verwenden möchten. Der Sisk SSL-Proxy ist ein Modul, das eine HTTPS-Verbindung für einen [ListeningHost](https://docs.sisk-framework.org/api/Sisk.Core.Http.ListeningHost.md) in Sisk bereitstellt und HTTPS-Nachrichten an einen unsicheren HTTP-Kontext weiterleitet. Das Modul wurde erstellt, um eine SSL-Verbindung für einen Dienst bereitzustellen, der [HttpListener](https://learn.microsoft.com/pt-br/dotnet/api/system.net.httplistener?view=net-8.0) verwendet, um zu ausgeführt zu werden, was keine SSL-Unterstützung bietet. Der Proxy läuft innerhalb der gleichen Anwendung und hört auf HTTP/1.1-Nachrichten, die im gleichen Protokoll an Sisk weitergeleitet werden. Derzeit ist diese Funktion sehr experimentell und möglicherweise so instabil, dass sie nicht in der Produktion verwendet werden sollte. Derzeit unterstützt der SslProxy fast alle HTTP/1.1-Features, wie z. B. Keep-Alive, Chunked-Encoding, WebSockets usw. Für eine offene Verbindung zum SSL-Proxy wird eine TCP-Verbindung zum Zielserver erstellt und der Proxy wird an die etablierte Verbindung weitergeleitet. Der SslProxy kann mit HttpServer.CreateBuilder wie folgt verwendet werden: ```csharp using var app = HttpServer.CreateBuilder(port: 5555) .UseRouter(r => { r.MapGet("/", request => { return new HttpResponse("Hallo, Welt!"); }); }) // SSL zum Projekt hinzufügen .UseSsl( sslListeningPort: 5567, new X509Certificate2(@".\ssl.pfx", password: "12345") ) .Build(); app.Start(); ``` Sie müssen ein gültiges SSL-Zertifikat für den Proxy bereitstellen. Um sicherzustellen, dass das Zertifikat von Browsern akzeptiert wird, importieren Sie es in das Betriebssystem, damit es ordnungsgemäß funktioniert. --- # Basic Auth Source: https://docs.sisk-framework.org/de/docs/extensions/basic-auth.html Das Basic-Auth-Paket fügt einen Anfrage-Handler hinzu, der in der Lage ist, das Basic-Authentifizierungsschema in Ihrer Sisk-Anwendung mit sehr wenig Konfiguration und Aufwand zu handhaben. Basic-HTTP-Authentifizierung ist eine minimale Eingabeform der Authentifizierung von Anfragen durch eine Benutzer-ID und ein Passwort, wobei die Sitzung ausschließlich vom Client gesteuert wird und es keine Authentifizierungs- oder Zugriffstoken gibt. ![Basic Auth](https://docs.sisk-framework.org/assets/img/basic-auth.svg) Erfahren Sie mehr über das Basic-Authentifizierungsschema in der [MDN-Spezifikation](https://developer.mozilla.org/pt-BR/docs/de/Web/HTTP/Authentication). ## Installation Um loszulegen, installieren Sie das Sisk.BasicAuth-Paket in Ihrem Projekt: > dotnet add package Sisk.BasicAuth Sie können mehrere Möglichkeiten zur Installation in Ihrem Projekt im [Nuget-Repository](https://www.nuget.org/packages/Sisk.BasicAuth/0.15.0) anzeigen. ## Erstellen Ihres Auth-Handlers Sie können das Authentifizierungsschema für ein ganzes Modul oder für einzelne Routen steuern. Dazu schreiben wir zunächst unseren ersten Basic-Authentifizierungs-Handler. Im folgenden Beispiel wird eine Verbindung zur Datenbank hergestellt, es wird überprüft, ob der Benutzer existiert und ob das Passwort gültig ist, und anschließend wird der Benutzer im Kontextbeutel gespeichert. ```cs public class UserAuthHandler : BasicAuthenticateRequestHandler { public UserAuthHandler() : base() { Realm = "Um diese Seite zu betreten, geben Sie bitte Ihre Anmeldeinformationen ein."; } public override HttpResponse? OnValidating(BasicAuthenticationCredentials credentials, HttpContext context) { DbContext db = new DbContext(); // In diesem Fall verwenden wir die E-Mail-Adresse als Benutzer-ID-Feld, also suchen wir nach einem Benutzer mit seiner E-Mail-Adresse. User? user = db.Users.FirstOrDefault(u => u.Email == credentials.UserId); if (user == null) { return base.CreateUnauthorizedResponse("Entschuldigung! Kein Benutzer mit dieser E-Mail-Adresse gefunden."); } // Überprüft, ob das Passwort für diesen Benutzer gültig ist. if (!user.ValidatePassword(credentials.Password)) { return base.CreateUnauthorizedResponse("Ungültige Anmeldeinformationen."); } // Fügt den angemeldeten Benutzer zum HTTP-Kontext hinzu // und setzt die Ausführung fort context.Bag.Add("loggedUser", user); return null; } } ``` Assoziieren Sie also diesen Anfrage-Handler mit unserer Route oder Klasse. ```cs public class UsersController { [RouteGet("/")] [RequestHandler(typeof(UserAuthHandler))] public string Index(HttpRequest request) { User loggedUser = request.Bag.Get(); return $"Hallo, {loggedUser.Name}!"; } } ``` Oder mit der [RouterModule](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouterModule.md)-Klasse: ```cs public class UsersController : RouterModule { public ClientModule() { // Alle Routen in dieser Klasse werden von // UserAuthHandler gehandhabt. base.HasRequestHandler(new UserAuthHandler()); } [RouteGet("/")] public string Index(HttpRequest request) { User loggedUser = request.Bag.Get(); return $"Hallo, {loggedUser.Name}!"; } } ``` ## Hinweise Die primäre Verantwortung für die Basic-Authentifizierung liegt auf der Client-Seite. Speicherung, Cache-Steuerung und Verschlüsselung werden alle lokal auf dem Client gehandhabt. Der Server erhält nur die Anmeldeinformationen und überprüft, ob der Zugriff erlaubt ist oder nicht. Beachten Sie, dass diese Methode nicht eine der sichersten ist, da sie eine erhebliche Verantwortung auf den Client legt, der schwierig zu verfolgen und die Sicherheit seiner Anmeldeinformationen zu gewährleisten ist. Darüber hinaus ist es wichtig, dass Passwörter in einem sicheren Verbindungskontext (SSL) übertragen werden, da sie keine inhärente Verschlüsselung haben. Eine kurze Abfangung der Header einer Anfrage kann die Zugriffsanmeldeinformationen Ihres Benutzers offenlegen. Wählen Sie für Produktionsanwendungen robustere Authentifizierungslösungen und vermeiden Sie die Verwendung von zu vielen vorgefertigten Komponenten, da diese möglicherweise nicht an die Bedürfnisse Ihres Projekts angepasst sind und es letztendlich Sicherheitsrisiken aussetzen. --- # Diensteanbieter Source: https://docs.sisk-framework.org/de/docs/extensions/service-providers.html Diensteanbieter sind eine Möglichkeit, Ihre Sisk-Anwendung mit einer portablen Konfigurationsdatei auf verschiedene Umgebungen zu übertragen. Diese Funktion ermöglicht es Ihnen, den Serverport, Parameter und andere Optionen ohne Änderung des Anwendungscode für jede Umgebung zu ändern. Dieses Modul hängt von der Sisk-Konstruktionsyntax ab und kann über die Methode `UsePortableConfiguration` konfiguriert werden. Ein Konfigurationsanbieter wird mit `IConfigurationProvider` implementiert, der einen Konfigurationsleser bereitstellt und jede Implementierung erhalten kann. Standardmäßig bietet Sisk einen JSON-Konfigurationsleser an, es gibt jedoch auch ein Paket für INI-Dateien. Sie können auch Ihren eigenen Konfigurationsanbieter erstellen und ihn mit: ```csharp using var app = HttpServer.CreateBuilder() .UsePortableConfiguration(config => { config.WithConfigReader(); }) .Build(); ``` Wie bereits erwähnt, ist der Standardanbieter eine JSON-Datei. Standardmäßig wird nach einer Datei mit dem Namen `service-config.json` gesucht, und diese wird im aktuellen Verzeichnis des laufenden Prozesses und nicht im Verzeichnis der ausführbaren Datei gesucht. Sie können den Dateinamen sowie das Verzeichnis, in dem Sisk nach der Konfigurationsdatei suchen soll, mit: ```csharp using Sisk.Core.Http; using Sisk.Core.Http.Hosting; using var app = HttpServer.CreateBuilder() .UsePortableConfiguration(config => { config.WithConfigFile("config.toml", createIfDontExists: true, lookupDirectories: ConfigurationFileLookupDirectory.CurrentDirectory | ConfigurationFileLookupDirectory.AppDirectory); }) .Build(); ``` Der obige Code sucht nach der Datei `config.toml` im aktuellen Verzeichnis des laufenden Prozesses. Wenn diese nicht gefunden wird, sucht er dann im Verzeichnis, in dem die ausführbare Datei liegt. Wenn die Datei nicht existiert, wird der Parameter `createIfDontExists` beachtet, der die Datei ohne Inhalt im letzten getesteten Pfad (basierend auf `lookupDirectories`) erstellt, und ein Fehler wird in der Konsole ausgegeben, was die Initialisierung der Anwendung verhindert. > [!TIP] > > Sie können den Quellcode des INI-Konfigurationslesers und des JSON-Konfigurationslesers betrachten, um zu verstehen, wie ein `IConfigurationProvider` implementiert wird. ## Lesen von Konfigurationen aus einer JSON-Datei Standardmäßig bietet Sisk einen Konfigurationsanbieter, der Konfigurationen aus einer JSON-Datei liest. Diese Datei folgt einer festen Struktur und besteht aus den folgenden Parametern: ```json { "Server": { "DefaultEncoding": "UTF-8", "ThrowExceptions": true, "IncludeRequestIdHeader": true }, "ListeningHost": { "Label": "Meine Sisk-Anwendung", "Ports": [ "http://localhost:80/", "https://localhost:443/", // Konfigurationsdateien unterstützen auch Kommentare ], "CrossOriginResourceSharingPolicy": { "AllowOrigin": "*", "AllowOrigins": [ "*" ], // neu in 0.14 "AllowMethods": [ "*" ], "AllowHeaders": [ "*" ], "MaxAge": 3600 }, "Parameters": { "MySqlConnection": "server=localhost;user=root;" } } } ``` Die aus einer Konfigurationsdatei erstellten Parameter können im Serverkonstruktor abgerufen werden: ```csharp using var app = HttpServer.CreateBuilder() .UsePortableConfiguration(config => { config.WithParameters(paramCollection => { string databaseConnection = paramCollection.GetValueOrThrow("MySqlConnection"); }); }) .Build(); ``` Jeder Konfigurationsleser bietet eine Möglichkeit, die Serverinitialisierungsparameter zu lesen. Einige Eigenschaften sind so konzipiert, dass sie in der Prozessumgebung anstelle der Konfigurationsdatei definiert werden, wie z. B. sensible API-Daten, API-Schlüssel usw. ## Konfigurationsdateistruktur Die JSON-Konfigurationsdatei besteht aus den folgenden Eigenschaften:
    Eigenschaft Pflichtfeld Beschreibung
    Server Erforderlich Stellt den Server selbst mit seinen Einstellungen dar.
    Server.AccessLogsStream Optional Standardmäßig console. Gibt den Ausgabestream für die Zugriffsprotokolle an. Kann ein Dateiname, null oder console sein.
    Server.ErrorsLogsStream Optional Standardmäßig null. Gibt den Ausgabestream für die Fehlerprotokolle an. Kann ein Dateiname, null oder console sein.
    Server.MaximumContentLength Optional
    Server.MaximumContentLength Optional Standardmäßig 0. Gibt die maximale Inhaltslänge in Bytes an. Null bedeutet unendlich.
    Server.IncludeRequestIdHeader Optional Standardmäßig false. Gibt an, ob der HTTP-Server den X-Request-Id-Header senden soll.
    Server.ThrowExceptions Optional Standardmäßig true. Gibt an, ob unbehandelte Ausnahmen ausgelöst werden sollen. Auf false setzen, wenn in der Produktion, und auf true, wenn beim Debuggen.
    ListeningHost Erforderlich Stellt den Server-Host dar, der zugehört.
    ListeningHost.Label Optional Stellt das Anwendungslabel dar.
    ListeningHost.Ports Erforderlich Stellt ein Array von Zeichenfolgen dar, die der Syntax von ListeningPort entsprechen.
    ListeningHost.CrossOriginResourceSharingPolicy Optional Konfiguriert die CORS-Header für die Anwendung.
    ListeningHost.CrossOriginResourceSharingPolicy.AllowCredentials Optional Standardmäßig false. Gibt den Allow-Credentials-Header an.
    ListeningHost.CrossOriginResourceSharingPolicy.ExposeHeaders Optional Standardmäßig null. Erwartet ein Array von Zeichenfolgen. Gibt den Expose-Headers-Header an.
    ListeningHost.CrossOriginResourceSharingPolicy.AllowOrigin Optional Standardmäßig null. Erwartet eine Zeichenfolge. Gibt den Allow-Origin-Header an.
    ListeningHost.CrossOriginResourceSharingPolicy.AllowOrigins Optional Standardmäßig null. Erwartet ein Array von Zeichenfolgen. Gibt mehrere Allow-Origin-Header an. Siehe AllowOrigins für weitere Informationen.
    ListeningHost.CrossOriginResourceSharingPolicy.AllowMethods Optional Standardmäßig null. Erwartet ein Array von Zeichenfolgen. Gibt den Allow-Methods-Header an.
    ListeningHost.CrossOriginResourceSharingPolicy.AllowHeaders Optional Standardmäßig null. Erwartet ein Array von Zeichenfolgen. Gibt den Allow-Headers-Header an.
    ListeningHost.CrossOriginResourceSharingPolicy.MaxAge Optional Standardmäßig null. Erwartet eine Ganzzahl. Gibt den Max-Age-Header in Sekunden an.
    ListeningHost.Parameters Optional Gibt die Eigenschaften an, die der Anwendungskonfigurationsmethode bereitgestellt werden.
    --- # INI-Konfiguration Source: https://docs.sisk-framework.org/de/docs/extensions/ini-configuration.html Sisk hat eine Methode, um Startkonfigurationen zu erhalten, die nicht JSON sind. Tatsächlich kann jede Pipeline, die [IConfigurationReader](https://docs.sisk-framework.org/api/Sisk.Core.Http.Hosting.IConfigurationReader.md) implementiert, mit [PortableConfigurationBuilder.WithConfigurationPipeline](https://docs.sisk-framework.org/api/Sisk.Core.Http.Hosting.PortableConfigurationBuilder.md) verwendet werden, um die Serverkonfiguration aus jeder Dateityp zu lesen. Das [Sisk.IniConfiguration](https://www.nuget.org/packages/Sisk.IniConfiguration/)-Paket bietet einen streambasierten INI-Dateileser, der keine Ausnahmen für häufige Syntaxfehler auslöst und eine einfache Konfigurationssyntax hat. Dieses Paket kann außerhalb des Sisk-Frameworks verwendet werden und bietet Flexibilität für Projekte, die einen effizienten INI-Dokumentleser benötigen. ## Installation Um das Paket zu installieren, können Sie mit folgendem Befehl beginnen: ```bash $ dotnet add package Sisk.IniConfiguration ``` Sie können auch das Core-Paket installieren, das weder den INI-[IConfigurationReader](https://docs.sisk-framework.org/api/Sisk.Core.Http.Hosting.IConfigurationReader) noch die Sisk-Abhängigkeit enthält, sondern nur die INI-Serialisierer: ```bash $ dotnet add package Sisk.IniConfiguration.Core ``` Mit dem Hauptpaket können Sie es in Ihrem Code wie im folgenden Beispiel verwenden: ```cs class Program { static HttpServerHostContext Host = null!; static void Main(string[] args) { Host = HttpServer.CreateBuilder() .UsePortableConfiguration(config => { config.WithConfigFile("app.ini", createIfDontExists: true); // verwendet den IniConfigurationReader-Konfigurationsleser config.WithConfigurationPipeline(); }) .UseRouter(r => { r.MapGet("/", SayHello); }) .Build(); Host.Start(); } static HttpResponse SayHello(HttpRequest request) { string? name = Host.Parameters["name"] ?? "world"; return new HttpResponse($"Hallo, {name}!"); } } ``` Der obige Code sucht nach einer app.ini-Datei im aktuellen Verzeichnis des Prozesses (CurrentDirectory). Die INI-Datei sieht wie folgt aus: ```ini [Server] # Mehrere Zuhöradressen werden unterstützt Listen = http://localhost:5552/ Listen = http://localhost:5553/ ThrowExceptions = false AccessLogsStream = console [Cors] AllowMethods = GET, POST AllowHeaders = Content-Type, Authorization AllowOrigin = * [Parameters] Name = "Kanye West" ``` ## INI-Geschmack und Syntax Aktuelle Implementierung des Geschmacks: - Eigenschaften- und Sektionsnamen sind **groß-/kleinschreibungsunabhängig**. - Eigenschaftsnamen und Werte werden **entfernt**, sofern Werte nicht in Anführungszeichen gesetzt sind. - Werte können mit einfachen oder doppelten Anführungszeichen umschlossen werden. Anführungszeichen können Zeilenumbrüche enthalten. - Kommentare werden mit `#` und `;` unterstützt. **Nachgestellte Kommentare sind ebenfalls erlaubt**. - Eigenschaften können mehrere Werte haben. Im Detail ist die Dokumentation für den "Geschmack" des INI-Parsers, der in Sisk verwendet wird, [in diesem Dokument verfügbar](https://github.com/sisk-http/archive/blob/master/ext/ini-reader-syntax.md). Mit dem folgenden INI-Code als Beispiel: ```ini One = 1 Value = this is an value Another value = "this value has an line break on it" ; der Code unten hat einige Farben [some section] Color = Red Color = Blue Color = Yellow ; verwenden Sie nicht gelb ``` Parse es mit: ```csharp // parse die INI-Text aus der Zeichenfolge IniDocument doc = IniDocument.FromString(iniText); // erhalten Sie einen Wert string? one = doc.Global.GetOne("one"); string? anotherValue = doc.Global.GetOne("another value"); // erhalten Sie mehrere Werte string[]? colors = doc.GetSection("some section")?.GetMany("color"); ``` ## Konfigurationsparameter | Sektion und Name | Erlaubt mehrere Werte | Beschreibung | | ---------------- | --------------------- | ----------- | | `Server.Listen` | Ja | Die Zuhöradressen/Ports des Servers. | | `Server.Encoding` | Nein | Die Standardcodierung des Servers. | | `Server.MaximumContentLength` | Nein | Die maximale Inhaltslänge des Servers in Bytes. | | `Server.IncludeRequestIdHeader` | Nein | Gibt an, ob der HTTP-Server die X-Request-Id-Header senden soll. | | `Server.ThrowExceptions` | Nein | Gibt an, ob unbehandelte Ausnahmen ausgelöst werden sollen. | | `Server.AccessLogsStream` | Nein | Gibt den Ausgabestream für die Zugriffsprotokolle an. | | `Server.ErrorsLogsStream` | Nein | Gibt den Ausgabestream für die Fehlerprotokolle an. | | `Cors.AllowMethods` | Nein | Gibt den Wert des CORS-Allow-Methods-Headers an. | | `Cors.AllowHeaders` | Nein | Gibt den Wert des CORS-Allow-Headers-Headers an. | | `Cors.AllowOrigins` | Nein | Gibt mehrere Allow-Origin-Header, getrennt durch Kommata, an. [AllowOrigins](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AllowOrigins.md) für weitere Informationen. | | `Cors.AllowOrigin` | Nein | Gibt einen Allow-Origin-Header an. | | `Cors.ExposeHeaders` | Nein | Gibt den Wert des CORS-Expose-Headers-Headers an. | | `Cors.AllowCredentials` | Nein | Gibt den Wert des CORS-Allow-Credentials-Headers an. | | `Cors.MaxAge` | Nein | Gibt den Wert des CORS-Max-Age-Headers an. --- # API-Dokumentation Source: https://docs.sisk-framework.org/de/docs/extensions/api-documentation.html Die `Sisk.Documenting`‑Erweiterung ermöglicht es Ihnen, automatisch API‑Dokumentation für Ihre Sisk‑Anwendung zu erzeugen. Sie nutzt Ihre Code‑Struktur und Attribute, um eine umfassende Dokumentations‑Website zu erstellen, die den Export ins Open API‑Format (Swagger) unterstützt. > [!WARNING] > Dieses Paket befindet sich derzeit in der Entwicklung und ist noch nicht veröffentlicht. Sein Verhalten und die API können sich in zukünftigen Updates ändern. Da dieses Paket noch nicht auf NuGet verfügbar ist, müssen Sie den Quellcode direkt in Ihr Projekt einbinden oder es als Projekt‑Abhängigkeit referenzieren. Sie können den Quellcode [hier](https://github.com/sisk-http/core/tree/main/extensions/Sisk.Documenting) abrufen. Um `Sisk.Documenting` zu verwenden, müssen Sie es in Ihrem Application‑Builder registrieren und Ihre Routinen‑Handler mit Dokumentations‑Attributen versehen. ### Registrieren der Dokumentationsgenerierung Verwenden Sie die Erweiterungsmethode `UseApiDocumentation` auf Ihrem `HttpServerHostContextBuilder`, um die generierte API‑Dokumentation über denselben Router bereitzustellen, der Ihre Anwendung bedient. ```csharp using Sisk.Documenting; using Sisk.Documenting.Exporters; // ... host.UseApiDocumentation( context: new ApiGenerationContext() { ApplicationName = "My Application", ApplicationDescription = "Description of my application.", ApplicationVersion = "1.0.0" }, routerPath: "/api/docs", exporter: new OpenApiExporter() { ServerUrls = ["http://localhost:5555/"] }); ``` - **context**: Definiert Metadaten über Ihre Anwendung, wie Name, Beschreibung und Version. - **routerPath**: Der URL‑Pfad, unter dem die Dokumentations‑Benutzeroberfläche (oder JSON) erreichbar ist. - **exporter**: Konfiguriert, wie die Dokumentation exportiert wird. Der `OpenApiExporter` ermöglicht die Unterstützung von Open API (Swagger). ### Dokumentieren von Endpunkten Sie können Ihre Endpunkte mit den Attributen `[ApiEndpoint]` und `[ApiQueryParameter]` auf Ihren Routinen‑Handler‑Methoden beschreiben. ### `ApiEndpoint` Das `[ApiEndpoint]`‑Attribut erlaubt es Ihnen, eine Beschreibung für den Endpunkt anzugeben. ```csharp [ApiEndpoint(Description = "Returns a greeting message.")] public HttpResponse Index(HttpRequest request) { ... } ``` ### `ApiQueryParameter` Das `[ApiQueryParameter]`‑Attribut dokumentiert Abfrage‑String‑Parameter, die der Endpunkt akzeptiert. ```csharp [ApiQueryParameter(name: "name", IsRequired = false, Description = "The name of the person to greet.", Type = "string")] public HttpResponse Index(HttpRequest request) { ... } ``` - **name**: Der Name des Abfrage‑Parameters. - **IsRequired**: Gibt an, ob der Parameter zwingend erforderlich ist. - **Description**: Eine menschenlesbare Beschreibung des Parameters. - **Type**: Der erwartete Datentyp (z. B. „string“, „int“). ### `ApiEndpoint` Annotiert einen Endpunkt mit allgemeinen Informationen. * **Name** (string, required in constructor): Der Name des API‑Endpunkts. * **Description** (string): Eine kurze Beschreibung dessen, was der Endpunkt tut. * **Group** (string): Ermöglicht das Gruppieren von Endpunkten (z. B. nach Controller oder Modul). * **InheritDescriptionFromXmlDocumentation** (bool, default: `true`): Wenn `true`, wird versucht, die XML‑Dokumentations‑Zusammenfassung der Methode zu verwenden, falls `Description` nicht gesetzt ist. ### `ApiHeader` Dokumentiert einen spezifischen HTTP‑Header, den der Endpunkt erwartet oder verwendet. * **HeaderName** (string, required in constructor): Der Schlüssel des Headers (z. B. „Authorization“). * **Description** (string): Beschreibt den Zweck des Headers. * **IsRequired** (bool): Gibt an, ob der Header für die Anfrage zwingend erforderlich ist. ### `ApiParameter` Definiert einen generischen Parameter für den Endpunkt, häufig verwendet für Formularfelder oder Body‑Parameter, die nicht durch andere Attribute abgedeckt sind. * **Name** (string, required in constructor): Der Name des Parameters. * **TypeName** (string, required in constructor): Der Datentyp des Parameters (z. B. „string“, „int“). * **Description** (string): Eine Beschreibung des Parameters. * **IsRequired** (bool): Gibt an, ob der Parameter zwingend erforderlich ist. ### `ApiParametersFrom` Erzeugt automatisch Parameter‑Dokumentation aus den Eigenschaften einer angegebenen Klasse oder eines Typs. * **Type** (Type, required in constructor): Der Klassen‑`Type`, aus dem die Eigenschaften reflektiert werden sollen. ### `ApiPathParameter` Dokumentiert eine Pfad‑Variable (z. B. in `/users/{id}`). * **Name** (string, required in constructor): Der Name des Pfad‑Parameters. * **Description** (string): Beschreibt, was der Parameter repräsentiert. * **Type** (string): Der erwartete Datentyp. ### `ApiQueryParameter` Dokumentiert einen Abfrage‑String‑Parameter (z. B. `?page=1`). * **Name** (string, required in constructor): Der Schlüssel des Abfrage‑Parameters. * **Description** (string): Beschreibt den Parameter. * **Type** (string): Der erwartete Datentyp. * **IsRequired** (bool): Gibt an, ob der Abfrage‑Parameter zwingend vorhanden sein muss. ### `ApiRequest` Beschreibt den erwarteten Request‑Body. * **Description** (string, required in constructor): Eine Beschreibung des Request‑Bodies. * **Example** (string): Ein Roh‑String, der ein Beispiel des Request‑Bodies enthält. * **ExampleLanguage** (string): Die Sprache des Beispiels (z. B. „json“, „xml“). * **PayloadType** (Type): Falls gesetzt, werden Beispiel und Schema automatisch aus diesem Typ generiert, sofern die konfigurierten Kontext‑Handler dies unterstützen. ### `ApiResponse` Beschreibt eine mögliche Antwort des Endpunkts. * **StatusCode** (HttpStatusCode, required in constructor): Der zurückgegebene HTTP‑Statuscode (z. B. `HttpStatusCode.OK`). * **Description** (string): Beschreibt die Bedingung für diese Antwort. * **Example** (string): Ein Roh‑String, der ein Beispiel des Antwort‑Bodies enthält. * **ExampleLanguage** (string): Die Sprache des Beispiels. * **PayloadType** (Type): Falls gesetzt, werden Beispiel und Schema automatisch aus diesem Typ generiert, sofern die konfigurierten Kontext‑Handler dies unterstützen. ## Typ-Handler Typ‑Handler sind dafür verantwortlich, Ihre .NET‑Typen (Klassen, Enums usw.) in Dokumentations‑Beispiele zu konvertieren. Das ist besonders nützlich, um automatische Request‑ und Response‑Body‑Beispiele basierend auf Ihren Datenmodellen zu erzeugen. Diese Handler werden innerhalb des `ApiGenerationContext` konfiguriert. ```csharp using Sisk.Documenting.Content; var context = new ApiGenerationContext() { // ... BodyExampleTypeHandler = new JsonContentTypeHandler(), ParameterExampleTypeHandler = new JsonContentTypeHandler(), ContentSchemaTypeHandler = new JsonContentTypeHandler() }; ``` ### JsonContentTypeHandler Der `JsonContentTypeHandler` ist ein integrierter Handler, der JSON‑Beispiele, Parameter‑Beispiele und JSON‑Schemas erzeugt. Er implementiert `IExampleBodyTypeHandler`, `IExampleParameterTypeHandler` und `IContentSchemaTypeHandler`. Er kann mit spezifischen `JsonSerializerOptions` oder `IJsonTypeInfoResolver` angepasst werden, um die Serialisierungs‑Logik Ihrer Anwendung zu berücksichtigen. ```csharp var jsonHandler = new JsonContentTypeHandler(new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, WriteIndented = true }); context.BodyExampleTypeHandler = jsonHandler; context.ParameterExampleTypeHandler = jsonHandler; context.ContentSchemaTypeHandler = jsonHandler; ``` ### Custom Type Handlers Sie können eigene Handler implementieren, um andere Formate (wie XML) zu unterstützen oder um zu steuern, wie Beispiele generiert werden. #### IExampleBodyTypeHandler Implementieren Sie dieses Interface, um Body‑Beispiele für Request‑ und Response‑Typen zu erzeugen. ```csharp public class XmlExampleTypeHandler : IExampleBodyTypeHandler { public BodyExampleResult? GetBodyExampleForType(Type type) { // Generate XML string for the type string xmlContent = MyXmlGenerator.Generate(type); return new BodyExampleResult(xmlContent, "xml"); } } ``` #### IExampleParameterTypeHandler Implementieren Sie dieses Interface, um detaillierte Parameter‑Beschreibungen aus einem Typ zu erzeugen (verwendet von `[ApiParametersFrom]`). ```csharp public class CustomParameterHandler : IExampleParameterTypeHandler { public ParameterExampleResult[] GetParameterExamplesForType(Type type) { var properties = type.GetProperties(); var examples = new List(); foreach (var prop in properties) { examples.Add(new ParameterExampleResult( name: prop.Name, typeName: prop.PropertyType.Name, isRequired: true, description: "Generated description" )); } return examples.ToArray(); } } ``` ## Exporters Exporter sind dafür verantwortlich, die gesammelten API‑Dokumentations‑Metadaten in ein bestimmtes Format zu konvertieren, das von anderen Tools konsumiert oder dem Benutzer angezeigt werden kann. ### OpenApiExporter Der standardmäßig bereitgestellte Exporter ist der `OpenApiExporter`, der eine JSON‑Datei gemäß der [OpenAPI Specification 3.0.0](https://spec.openapis.org/oas/v3.0.0) erzeugt. ```csharp new OpenApiExporter() { OpenApiVersion = "3.0.0", ServerUrls = new[] { "http://localhost:5555" }, Contact = new OpenApiContact() { Name = "Support", Email = "support@example.com", Url = "https://example.com/support" }, License = new OpenApiLicense() { Name = "MIT", Url = "https://opensource.org/licenses/MIT" }, TermsOfService = "https://example.com/terms" } ``` ### Creating a Custom Exporter Sie können Ihren eigenen Exporter erstellen, indem Sie das Interface `IApiDocumentationExporter` implementieren. Damit können Sie die Dokumentation in Formaten wie Markdown, HTML, Postman Collection oder einem anderen benutzerdefinierten Format ausgeben. Das Interface verlangt die Implementierung einer einzigen Methode: `ExportDocumentationContent`. ```csharp using Sisk.Core.Http; using Sisk.Documenting; public class MyCustomExporter : IApiDocumentationExporter { public HttpContent ExportDocumentationContent(ApiDocumentation documentation) { // 1. Process the documentation object var sb = new StringBuilder(); sb.AppendLine($"# {documentation.ApplicationName}"); foreach(var endpoint in documentation.Endpoints) { sb.AppendLine($"## {endpoint.Method} {endpoint.Path}"); sb.AppendLine(endpoint.Description); } // 2. Return the content as an HttpContent return new StringContent(sb.ToString(), Encoding.UTF8, "text/markdown"); } } ``` Dann verwenden Sie ihn einfach in Ihrer Konfiguration: ```csharp host.UseApiDocumentation( // ... exporter: new MyCustomExporter() ); ``` ### Full Example Unten finden Sie ein vollständiges Beispiel, das zeigt, wie `Sisk.Documenting` eingerichtet und ein einfacher Controller dokumentiert wird. ```csharp using Sisk.Core.Entity; using Sisk.Core.Http; using Sisk.Core.Routing; using Sisk.Documenting; using Sisk.Documenting.Annotations; using Sisk.Documenting.Exporters; using var host = HttpServer.CreateBuilder(5555) .UseCors(CrossOriginResourceSharingHeaders.CreatePublicContext()) .UseApiDocumentation( context: new ApiGenerationContext() { ApplicationName = "My application", ApplicationDescription = "It greets someone." }, routerPath: "/api/docs", exporter: new OpenApiExporter() { ServerUrls = ["http://localhost:5555/"] }) .UseRouter(router => { router.MapInstance(new MyController()); }) .Build(); await host.StartAsync(); class MyController { [RouteGet] [ApiEndpoint(Description = "Returns a greeting message.")] [ApiQueryParameter(name: "name", IsRequired = false, Description = "The name of the person to greet.", Type = "string")] public HttpResponse Index(HttpRequest request) { string? name = request.Query["name"].MaybeNullOrEmpty() ?? "world"; return new HttpResponse($"Hello, {name}!"); } } ``` In diesem Beispiel liefert das Aufrufen von `/api/docs` die generierte Dokumentation für die API „My application“ und beschreibt den `GET /`‑Endpunkt sowie dessen `name`‑Parameter. --- # Manuelle (erweiterte) Einrichtung Source: https://docs.sisk-framework.org/de/docs/advanced/manual-setup.html Verwenden Sie die manuelle Einrichtung, wenn Sie die Serverkomponenten selbst zusammenbauen müssen, z. B. wenn ein Prozess mehrere Hosts, Ports, Router oder eine benutzerdefinierte Serverkonfiguration bereitstellen muss. Für die meisten Anwendungen ist die Builder‑API kürzer und sollte bevorzugt werden. Die manuelle Einrichtung ist nützlich, wenn Sie direkte Kontrolle über die vier Kernkomponenten haben wollen: einen `Router`, ein oder mehrere `ListeningHost`‑Objekte, eine `HttpServerConfiguration` und den finalen `HttpServer`. Zunächst müssen wir das Request/Response‑Konzept verstehen. Es ist ganz einfach: Für jede Anfrage muss es eine Antwort geben. Sisk folgt diesem Prinzip ebenfalls. Erstellen wir eine Methode, die mit einer „Hello, World!“-Nachricht in HTML antwortet und dabei den Statuscode sowie Header angibt. ```csharp // Program.cs using Sisk.Core.Http; using Sisk.Core.Routing; static HttpResponse IndexPage(HttpRequest request) { HttpResponse indexResponse = new HttpResponse { Status = System.Net.HttpStatusCode.OK, Content = new HtmlContent(@"

    Hello, world!

    ") }; return indexResponse; } ``` Der nächste Schritt ist, diese Methode mit einer HTTP‑Route zu verknüpfen. ## Router Router sind Abstraktionen von Anforderungsrouten und dienen als Brücke zwischen Anfragen und Antworten für den Dienst. Router verwalten Service‑Routen, Funktionen und Fehler. Ein Router kann mehrere Routen besitzen, und jede Route kann unterschiedliche Operationen auf diesem Pfad ausführen, z. B. eine Funktion ausführen, eine Seite bereitstellen oder eine Ressource vom Server liefern. Erstellen wir unseren ersten Router und verknüpfen die `IndexPage`‑Methode mit dem Index‑Pfad. ```csharp Router mainRouter = new Router(); mainRouter.MapGet("/", IndexPage); ``` Jetzt kann unser Router Anfragen empfangen und Antworten senden. Allerdings ist `mainRouter` nicht an einen Host oder Server gebunden, sodass er allein nicht funktioniert. Der nächste Schritt ist, unser `ListeningHost` zu erstellen. ## Listening‑Hosts und Ports Ein [ListeningHost](https://docs.sisk-framework.org/api/Sisk.Core.Http.ListeningHost.md) kann einen Router und mehrere Listening‑Ports für denselben Router hosten. Ein [ListeningPort](https://docs.sisk-framework.org/api/Sisk.Core.Http.ListeningPort.md) ist ein Präfix, an dem der HTTP‑Server lauscht. Hier können wir einen `ListeningHost` erstellen, der auf zwei Endpunkte für unseren Router zeigt: ```csharp ListeningHost myHost = new ListeningHost { Router = mainRouter, Ports = new ListeningPort[] { new ListeningPort("http://localhost:5000/") } }; ``` Jetzt wird unser HTTP‑Server an den angegebenen Endpunkten lauschen und die Anfragen an unseren Router weiterleiten. ## Serverkonfiguration Die Serverkonfiguration ist für das meiste Verhalten des HTTP‑Servers selbst verantwortlich. In dieser Konfiguration können wir `ListeningHosts` mit unserem Server verknüpfen. ```csharp HttpServerConfiguration config = new HttpServerConfiguration(); config.ListeningHosts.Add(myHost); // Fügt unseren ListeningHost zu dieser Serverkonfiguration hinzu ``` Gemeinsame Optionen der Serverkonfiguration: | Eigenschaft | Standard | Verwendung | Hinweise | | --- | --- | --- | --- | | [RemoteRequestsAction](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.RemoteRequestsAction.md) | `RequestListenAction.Accept` | Der Dienst sollte nicht‑lokale Anfragen ablehnen, es sei denn, sie kommen über einen vertrauenswürdigen Reverse‑Proxy. | Auf `Drop` setzen nur, wenn Ihre Bereitstellungstopologie klar ist. | | [IncludeRequestIdHeader](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.IncludeRequestIdHeader.md) | `false` | Clients oder Proxies benötigen die Sisk‑Request‑ID im `X-Request-Id`‑Response‑Header. | Kombinieren Sie dies mit Logs, die `HttpRequest.RequestId` enthalten. | | [IdleConnectionTimeout](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.IdleConnectionTimeout.md) | `120` Sekunden | Leerlauf‑Keep‑Alive‑Verbindungen sollten früher oder später geschlossen werden. | Dies wird von der HTTP‑Engine angewendet. | | [NormalizeHeadersEncodings](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.NormalizeHeadersEncodings.md) | `false` | Sie erhalten Header mit einer Kodierungsinkongruenz. | Dies verursacht Verarbeitungsaufwand; deaktivieren Sie es nur bei Bedarf. | | [SendSiskHeader](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.SendSiskHeader.md) | `true` | Sie möchten den `X-Powered-By`‑Sisk‑Header verbergen oder anzeigen. | Deaktivieren Sie ihn für strengere Produktions‑Header‑Richtlinien. | | [OptionsLogMode](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.OptionsLogMode.md) | `LogOutput.Both` | Sie möchten die durch automatische `OPTIONS`‑Verarbeitung erzeugten Logs reduzieren oder umleiten. | Verwendet dieselben Log‑Modus‑Werte wie Routen. | | [AsyncRequestProcessing](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.AsyncRequestProcessing.md) | `true` | Sie benötigen deterministische Einzel‑Request‑Verarbeitung für Diagnosen. | Das Deaktivieren reduziert den Durchsatz. | | [DisposeDisposableContextValues](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.DisposeDisposableContextValues.md) | `true` | Werte im Request‑Bag, die `IDisposable` implementieren, sollten automatisch entsorgt werden. | Aktiviert lassen, es sei denn, die Besitzverwaltung erfolgt anderswo. | | [ConvertIAsyncEnumerableIntoEnumerable](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ConvertIAsyncEnumerableIntoEnumerable.md) | `true` | Wert‑Handler sollten asynchrone Enumerables als blockierende Enumerables erhalten. | Deaktivieren, wenn Sie eigene Async‑Stream‑Verarbeitung implementieren. | | [KeepAlive](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.KeepAlive.md) | `true` | Verbindungen sollten nach Antworten wiederverwendbar bleiben. | Deaktivieren für Clients oder Zwischensysteme, die persistente Verbindungen schlecht handhaben. | | [ForceTrailingSlash](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ForceTrailingSlash.md) | `false` | GET‑Routen sollten zu einer URL mit abschließendem Slash umleiten. | Gilt nur für nicht‑regex‑basierte Routen. | | [MaximumContentLength](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.MaximumContentLength.md) | `0` | Anfragetexte benötigen ein Größenlimit. | `0` bedeutet unbegrenzt, bis Framework‑ oder Speichergrenzen erreicht sind. | | [EnableAutomaticResponseCompression](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.EnableAutomaticResponseCompression.md) | `false` | Antworten sollten automatisch komprimiert werden, wenn der Client dies unterstützt. | Bereits komprimierte `CompressedContent`‑Antworten werden nicht erneut komprimiert. | Als Nächstes können wir unseren HTTP‑Server erstellen: ```csharp HttpServer server = new HttpServer(config); server.Start(); // Startet den Server Console.ReadKey(); // Verhindert, dass die Anwendung beendet wird ``` Jetzt können wir die ausführbare Datei kompilieren und unseren HTTP‑Server mit dem Befehl starten: ```bash dotnet watch ``` Zur Laufzeit öffnen Sie Ihren Browser und navigieren zur Server‑URL; Sie sollten Folgendes sehen: --- # Anfragelebenszyklus Source: https://docs.sisk-framework.org/de/docs/advanced/request-lifecycle.html Im Folgenden wird der gesamte Lebenszyklus einer Anfrage anhand eines Beispiels einer HTTP-Anfrage erklärt. - **Empfangen der Anfrage:** Jede Anfrage erzeugt einen HTTP‑Kontext zwischen der Anfrage selbst und der Antwort, die dem Client zugestellt wird. Dieser Kontext stammt vom integrierten Listener in Sisk, der [HttpListener](https://learn.microsoft.com/en-us/dotnet/api/system.net.httplistener?view=net-9.0), [Kestrel](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel?view=aspnetcore-9.0) oder [Cadente](https://blog.sisk-framework.org/posts/2025-01-29-cadente-experiment/) sein kann. - Externe Anforderungsvalidierung: Die Validierung von [HttpServerConfiguration.RemoteRequestsAction](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.RemoteRequestsAction.md) wird für die Anfrage durchgeführt. - Wenn die Anfrage extern ist und die Eigenschaft `Drop` ist, wird die Verbindung ohne Antwort an den Client geschlossen mit einem `HttpServerExecutionStatus = RemoteRequestDropped`. - Forwarding‑Resolver‑Konfiguration: Wenn ein [ForwardingResolver](https://docs.sisk-framework.org/de/docs/advanced/forwarding-resolvers.md) konfiguriert ist, ruft er die Methode [OnResolveRequestHost](https://docs.sisk-framework.org/api/Sisk.Core.Http.ForwardingResolver.OnResolveRequestHost.md) auf dem ursprünglichen Host der Anfrage auf. - DNS‑Abgleich: Mit dem aufgelösten Host und mehr als einem konfigurierten [ListeningHost](https://docs.sisk-framework.org/api/Sisk.Core.Http.ListeningHost.md) sucht der Server nach dem entsprechenden Host für die Anfrage. - Wenn kein ListeningHost passt, wird eine 400 Bad Request‑Antwort an den Client zurückgegeben und ein `HttpServerExecutionStatus = DnsUnknownHost`‑Status an den HTTP‑Kontext zurückgegeben. - Wenn ein ListeningHost passt, dessen [Router](https://docs.sisk-framework.org/api/Sisk.Core.Http.ListeningHost.Router.md) jedoch noch nicht initialisiert ist, wird eine 503 Service Unavailable‑Antwort an den Client zurückgegeben und ein `HttpServerExecutionStatus = ListeningHostNotReady`‑Status an den HTTP‑Kontext zurückgegeben. - Router‑Bindung: Der Router des entsprechenden ListeningHost wird dem empfangenen HTTP‑Server zugeordnet. - Wenn der Router bereits einem anderen HTTP‑Server zugeordnet ist (was nicht erlaubt ist, weil der Router aktiv die Konfigurationsressourcen des Servers nutzt), wird eine `InvalidOperationException` ausgelöst. Dies geschieht nur während der Initialisierung des HTTP‑Servers, nicht während der Erstellung des HTTP‑Kontexts. - Vordefinition von Headern: - Definiert den Header `X-Request-Id` in der Antwort, wenn dies konfiguriert ist. - Definiert den Header `X-Powered-By` in der Antwort, wenn dies konfiguriert ist. - Inhaltsgrößen‑Validierung: Prüft, ob der Anforderungsinhalt kleiner ist als [HttpServerConfiguration.MaximumContentLength](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.MaximumContentLength.md), sofern dieser Wert größer als null ist. - Wenn die Anfrage einen `Content-Length`‑Wert sendet, der größer ist als der konfigurierte, wird eine 413 Payload Too Large‑Antwort an den Client zurückgegeben und ein `HttpServerExecutionStatus = ContentTooLarge`‑Status an den HTTP‑Kontext zurückgegeben. - Das Ereignis `OnHttpRequestOpen` wird für alle konfigurierten HTTP‑Server‑Handler aufgerufen. - **Routing der Aktion:** Der Server ruft den Router für die empfangene Anfrage auf. - Wenn der Router keine Route findet, die zur Anfrage passt: - Wenn die Eigenschaft [Router.NotFoundErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.NotFoundErrorHandler.md) konfiguriert ist, wird die Aktion aufgerufen und die Antwort der Aktion an den HTTP‑Client weitergeleitet. - Wenn die vorherige Eigenschaft null ist, wird eine standardmäßige 404 Not Found‑Antwort an den Client zurückgegeben. - Wenn der Router eine passende Route findet, die Methode der Route jedoch nicht mit der Methode der Anfrage übereinstimmt: - Wenn die Eigenschaft [Router.MethodNotAllowedErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.MethodNotAllowedErrorHandler.md) konfiguriert ist, wird die Aktion aufgerufen und die Antwort der Aktion an den HTTP‑Client weitergeleitet. - Wenn die vorherige Eigenschaft null ist, wird eine standardmäßige 405 Method Not Allowed‑Antwort an den Client zurückgegeben. - Wenn die Anfrage die Methode `OPTIONS` hat: - Gibt der Router nur dann eine 200 Ok‑Antwort an den Client zurück, wenn keine Route die Anfragemethode (die Route‑Methode ist nicht explizit [RouteMethod.Options](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouteMethod.md)) erfüllt. - Wenn die Eigenschaft [HttpServerConfiguration.ForceTrailingSlash](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ForceTrailingSlash.md) aktiviert ist, die gefundene Route kein Regex ist, der Anforderungspfad nicht mit `/` endet und die Anfragemethode `GET` ist: - Wird eine 307 Temporary Redirect‑HTTP‑Antwort mit dem `Location`‑Header, der Pfad und Query zur gleichen Adresse mit einem abschließenden `/` enthält, an den Client zurückgegeben. - Das Ereignis `OnContextBagCreated` wird für alle konfigurierten HTTP‑Server‑Handler aufgerufen. - Alle globalen [IRequestHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.IRequestHandler.md)‑Instanzen mit dem Flag `BeforeResponse` werden ausgeführt. - Gibt ein Handler eine nicht‑null‑Antwort zurück, wird diese Antwort an den HTTP‑Client weitergeleitet und der Kontext geschlossen. - Wird in diesem Schritt ein Fehler ausgelöst und ist [HttpServerConfiguration.ThrowExceptions](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ThrowExceptions.md) deaktiviert: - Ist die Eigenschaft [Router.CallbackErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.CallbackErrorHandler.md) aktiviert, wird sie aufgerufen und die resultierende Antwort an den Client zurückgegeben. - Wenn die vorherige Eigenschaft nicht definiert ist, wird eine leere Antwort an den Server zurückgegeben, der dann je nach Art der ausgelösten Ausnahme eine Antwort (meist 500 Internal Server Error) weiterleitet. - Alle [IRequestHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.IRequestHandler.md)‑Instanzen, die in der Route definiert und mit dem Flag `BeforeResponse` versehen sind, werden ausgeführt. - Gibt ein Handler eine nicht‑null‑Antwort zurück, wird diese Antwort an den HTTP‑Client weitergeleitet und der Kontext geschlossen. - Wird in diesem Schritt ein Fehler ausgelöst und ist [HttpServerConfiguration.ThrowExceptions](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ThrowExceptions.md) deaktiviert: - Ist die Eigenschaft [Router.CallbackErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.CallbackErrorHandler.md) aktiviert, wird sie aufgerufen und die resultierende Antwort an den Client zurückgegeben. - Wenn die vorherige Eigenschaft nicht definiert ist, wird eine leere Antwort an den Server zurückgegeben, der dann je nach Art der ausgelösten Ausnahme eine Antwort (meist 500 Internal Server Error) weiterleitet. - Die Aktion des Routers wird aufgerufen und in eine HTTP‑Antwort umgewandelt. - Wird in diesem Schritt ein Fehler ausgelöst und ist [HttpServerConfiguration.ThrowExceptions](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ThrowExceptions.md) deaktiviert: - Ist die Eigenschaft [Router.CallbackErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.CallbackErrorHandler.md) aktiviert, wird sie aufgerufen und die resultierende Antwort an den Client zurückgegeben. - Wenn die vorherige Eigenschaft nicht definiert ist, wird eine leere Antwort an den Server zurückgegeben, der dann je nach Art der ausgelösten Ausnahme eine Antwort (meist 500 Internal Server Error) weiterleitet. - Alle globalen [IRequestHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.IRequestHandler.md)‑Instanzen mit dem Flag `AfterResponse` werden ausgeführt. - Gibt ein Handler eine nicht‑null‑Antwort zurück, ersetzt die Antwort des Handlers die vorherige Antwort und wird sofort an den HTTP‑Client weitergeleitet. - Wird in diesem Schritt ein Fehler ausgelöst und ist [HttpServerConfiguration.ThrowExceptions](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ThrowExceptions.md) deaktiviert: - Ist die Eigenschaft [Router.CallbackErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.CallbackErrorHandler.md) aktiviert, wird sie aufgerufen und die resultierende Antwort an den Client zurückgegeben. - Wenn die vorherige Eigenschaft nicht definiert ist, wird eine leere Antwort an den Server zurückgegeben, der dann je nach Art der ausgelösten Ausnahme eine Antwort (meist 500 Internal Server Error) weiterleitet. - Alle [IRequestHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.IRequestHandler.md)‑Instanzen, die in der Route definiert und mit dem Flag `AfterResponse` versehen sind, werden ausgeführt. - Gibt ein Handler eine nicht‑null‑Antwort zurück, ersetzt die Antwort des Handlers die vorherige Antwort und wird sofort an den HTTP‑Client weitergeleitet. - Wird in diesem Schritt ein Fehler ausgelöst und ist [HttpServerConfiguration.ThrowExceptions](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ThrowExceptions.md) deaktiviert: - Ist die Eigenschaft [Router.CallbackErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.CallbackErrorHandler.md) aktiviert, wird sie aufgerufen und die resultierende Antwort an den Client zurückgegeben. - Wenn die vorherige Eigenschaft nicht definiert ist, wird eine leere Antwort an den Server zurückgegeben, der dann je nach Art der ausgelösten Ausnahme eine Antwort (meist 500 Internal Server Error) weiterleitet. - **Verarbeitung der Antwort:** Sobald die Antwort fertig ist, bereitet der Server sie für den Versand an den Client vor. - Die Cross‑Origin Resource Sharing‑Policy (CORS)‑Header werden in der Antwort gemäß der im aktuellen [ListeningHost.CrossOriginResourceSharingPolicy](https://docs.sisk-framework.org/api/Sisk.Core.Http.ListeningHost.CrossOriginResourceSharingPolicy.md) konfigurierten Richtlinie definiert. - Der Statuscode und die Header der Antwort werden an den Client gesendet. - Der Antwortinhalt wird an den Client gesendet: - Ist der Antwortinhalt ein Nachfolger von [ByteArrayContent](https://learn.microsoft.com/en-us/dotnet/api/system.net.http.bytearraycontent), werden die Antwort‑Bytes direkt in den Ausgabestream der Antwort kopiert. - Wird die vorherige Bedingung nicht erfüllt, wird die Antwort in einen Stream serialisiert und in den Ausgabestream der Antwort kopiert. - Die Streams werden geschlossen und der Antwortinhalt verworfen. - Ist [HttpServerConfiguration.DisposeDisposableContextValues](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.DisposeDisposableContextValues.md) aktiviert, werden alle im Anforderungskontext definierten Objekte, die von [IDisposable](https://learn.microsoft.com/en-us/dotnet/api/system.idisposable) erben, verworfen. - Das Ereignis `OnHttpRequestClose` wird für alle konfigurierten HTTP‑Server‑Handler aufgerufen. - Wird auf dem Server eine Ausnahme ausgelöst, wird das Ereignis `OnException` für alle konfigurierten HTTP‑Server‑Handler aufgerufen. - Erlaubt die Route das Zugriffs‑Logging und ist [HttpServerConfiguration.AccessLogsStream](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.AccessLogsStream.md) nicht null, wird eine Log‑Zeile in die Log‑Ausgabe geschrieben. - Erlaubt die Route das Fehler‑Logging, liegt eine Ausnahme vor und ist [HttpServerConfiguration.ErrorsLogsStream](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ErrorsLogsStream.md) nicht null, wird eine Log‑Zeile in die Fehler‑Log‑Ausgabe geschrieben. - Wartet der Server über [HttpServer.WaitNext](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServer.WaitNext.md) auf eine Anfrage, wird das Mutex freigegeben und der Kontext dem Benutzer wieder zur Verfügung gestellt. --- # Forwarding-Resolver Source: https://docs.sisk-framework.org/de/docs/advanced/forwarding-resolvers.html Ein Forwarding Resolver ist ein Helfer, der dabei unterstützt, Informationen zu dekodieren, die den Client über eine Anfrage, einen Proxy, ein CDN oder Load‑Balancer identifizieren. Wenn Ihr Sisk‑Dienst hinter einem Reverse‑ oder Forward‑Proxy läuft, können die IP‑Adresse, der Host und das Protokoll des Clients von der ursprünglichen Anfrage abweichen, da die Anfrage von einem Service zum anderen weitergeleitet wird. Diese Sisk‑Funktionalität ermöglicht es Ihnen, diese Informationen zu kontrollieren und zu ermitteln, bevor Sie mit der Anfrage arbeiten. Diese Proxies stellen in der Regel nützliche Header bereit, um ihren Client zu identifizieren. Derzeit ist es mit der Klasse [ForwardingResolver](https://docs.sisk-framework.org/api/Sisk.Core.Http.ForwardingResolver.md) möglich, die IP‑Adresse des Clients, den Host und das verwendete HTTP‑Protokoll zu ermitteln. Nach Version 1.0 von Sisk besitzt der Server keine standardisierte Implementierung mehr, um diese Header aus Sicherheitsgründen zu dekodieren, die von Service zu Service variieren. Beispielsweise enthält der Header `X-Forwarded-For` Informationen über die IP‑Adressen, die die Anfrage weitergeleitet haben. Dieser Header wird von Proxies verwendet, um eine Kette von Informationen bis zum Zielservice zu transportieren, und beinhaltet die IP aller genutzten Proxies, einschließlich der echten Adresse des Clients. Das Problem ist: Oft ist es schwierig, die entfernte IP des Clients zu bestimmen, und es gibt keine feste Regel, um diesen Header zu identifizieren. Es wird dringend empfohlen, die Dokumentation der Header, die Sie implementieren möchten, unten zu lesen: - Lesen Sie über den Header `X-Forwarded-For` [hier](https://developer.mozilla.org/en-US/docs/de/Web/HTTP/Headers/X-Forwarded-For#security_and_privacy_concerns). - Lesen Sie über den Header `X-Forwarded-Host` [hier](https://developer.mozilla.org/en-US/docs/de/Web/HTTP/Headers/X-Forwarded-Host). - Lesen Sie über den Header `X-Forwarded-Proto` [hier](https://developer.mozilla.org/en-US/docs/de/Web/HTTP/Headers/X-Forwarded-Proto). ## Die ForwardingResolver‑Klasse Diese Klasse verfügt über drei virtuelle Methoden, die die jeweils passendste Implementierung für jeden Service ermöglichen. Jede Methode ist dafür verantwortlich, Informationen aus der Anfrage über einen Proxy zu ermitteln: die IP‑Adresse des Clients, den Host der Anfrage und das verwendete Sicherheitsprotokoll. Standardmäßig verwendet Sisk immer die Informationen der ursprünglichen Anfrage, ohne irgendwelche Header zu verarbeiten. Das nachstehende Beispiel zeigt, wie diese Implementierung verwendet werden kann. Das Beispiel ermittelt die IP des Clients über den Header `X-Forwarded-For` und wirft einen Fehler, wenn mehr als eine IP in der Anfrage weitergeleitet wurde. > [!IMPORTANT] > Verwenden Sie dieses Beispiel nicht in Produktionscode. Prüfen Sie stets, ob die Implementierung für den jeweiligen Einsatz geeignet ist. Lesen Sie die Header‑Dokumentation, bevor Sie sie implementieren. ```cs class Program { static void Main(string[] args) { using var host = HttpServer.CreateBuilder() .UseForwardingResolver() .UseListeningPort(5555) .Build(); host.Router.MapAny(Route.AnyPath, request => new HttpResponse("Hello, world!!!")); host.Start(); } class Resolver : ForwardingResolver { public override IPAddress OnResolveClientAddress(HttpRequest request, IPEndPoint connectingEndpoint) { string? forwardedFor = request.Headers.XForwardedFor; if (forwardedFor is null) { throw new Exception("The X-Forwarded-For header is missing."); } string[] ipAddresses = forwardedFor.Split(','); if (ipAddresses.Length != 1) { throw new Exception("Too many addresses in the X-Forwarded-For header."); } return IPAddress.Parse(ipAddresses[0]); } } } ``` --- # Http-Server-Handler Source: https://docs.sisk-framework.org/de/docs/advanced/http-server-handlers.html In Sisk Version 0.16 haben wir die Klasse `HttpServerHandler` eingeführt, die das übergeordnete Verhalten von Sisk erweitern und zusätzliche Ereignis‑Handler bereitstellen soll, wie das Verarbeiten von Http‑Anfragen, Routern, Kontextbeuteln und mehr. Die Klasse bündelt Ereignisse, die während der Lebensdauer des gesamten HTTP‑Servers und auch einer einzelnen Anfrage auftreten. Das Http‑Protokoll besitzt keine Sitzungen, sodass es nicht möglich ist, Informationen von einer Anfrage zur nächsten zu erhalten. Sisk bietet derzeit eine Möglichkeit, Sitzungen, Kontexte, Datenbankverbindungen und andere nützliche Provider zu implementieren, um Ihre Arbeit zu erleichtern. Bitte beachten Sie [diese Seite](https://docs.sisk-framework.org/api/Sisk.Core.Http.Handlers.HttpServerHandler.md), um zu lesen, wann jedes Ereignis ausgelöst wird und welchen Zweck es hat. Sie können auch den [Lebenszyklus einer HTTP‑Anfrage](https://docs.sisk-framework.org/de/docs/advanced/request-lifecycle.md) einsehen, um zu verstehen, was bei einer Anfrage passiert und wo Ereignisse ausgelöst werden. Der HTTP‑Server erlaubt die gleichzeitige Verwendung mehrerer Handler. Jeder Ereignisaufruf ist synchron, das heißt, er blockiert den aktuellen Thread für jede Anfrage oder jeden Kontext, bis alle mit dieser Funktion verbundenen Handler ausgeführt und abgeschlossen sind. Im Gegensatz zu RequestHandlers können sie nicht auf bestimmte Routen‑Gruppen oder einzelne Routen angewendet werden. Stattdessen gelten sie für den gesamten HTTP‑Server. Sie können Bedingungen innerhalb Ihres Http‑Server‑Handlers festlegen. Darüber hinaus wird für jede Sisk‑Anwendung ein Singleton jedes `HttpServerHandler` definiert, sodass pro `HttpServerHandler` nur eine Instanz existiert. Ein praktisches Beispiel für die Verwendung von `HttpServerHandler` ist das automatische Freigeben einer Datenbankverbindung am Ende einer Anfrage. ```cs // DatabaseConnectionHandler.cs public class DatabaseConnectionHandler : HttpServerHandler { protected override void OnHttpRequestClose(HttpServerExecutionResult result) { var requestBag = result.Request.Context.RequestBag; // prüft, ob die Anfrage einen DbContext definiert hat // in ihrem Kontextbeutel if (requestBag.IsSet()) { var db = requestBag.Get(); db.Dispose(); } } } public static class DatabaseConnectionHandlerExtensions { public static DbContext GetDbContext(this HttpRequest request) { return request.Bag.GetOrAdd(() => new DbContext()); } } ``` Mit dem obigen Code ermöglicht die Erweiterung `GetDbContext` das Erstellen eines Verbindungs‑Contexts direkt aus dem `HttpRequest`‑Objekt. Eine nicht freigegebene Verbindung kann beim Betrieb mit der Datenbank Probleme verursachen, daher wird sie in `OnHttpRequestClose` beendet. Sie können einen Handler auf einem Http‑Server in Ihrem Builder oder direkt mit [HttpServer.RegisterHandler](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServer.RegisterHandler.md) registrieren. ```cs // Program.cs class Program { static void Main(string[] args) { using var app = HttpServer.CreateBuilder() .UseHandler() .Build(); app.Router.MapInstance(new UserController()); app.Start(); } } ``` Damit kann die Klasse `UsersController` den Datenbank‑Context wie folgt nutzen: ```cs // UserController.cs [RoutePrefix("/users")] public class UserController : ApiController { [RouteGet()] public async Task List(HttpRequest request) { var db = request.GetDbContext(); var users = db.Users.ToArray(); return JsonOk(users); } [RouteGet("")] public async Task View(HttpRequest request) { var db = request.GetDbContext(); int userId = request.RouteParameters["id"].GetInteger(); var user = db.Users.FirstOrDefault(u => u.Id == userId); return JsonOk(user); } [RoutePost] public async Task Create(HttpRequest request) { var db = request.GetDbContext(); var user = await request.GetJsonContentAsync(); ArgumentNullException.ThrowIfNull(user); db.Users.Add(user); await db.SaveChangesAsync(); return JsonMessage("User added."); } } ``` Der obige Code verwendet Methoden wie `JsonOk` und `JsonMessage`, die in `ApiController` eingebaut sind und von einem `RouterController` erben: ```cs // ApiController.cs public class ApiController : RouterModule { public HttpResponse JsonOk(object value) { return new HttpResponse(200) .WithContent(JsonContent.Create(value, null, new JsonSerializerOptions() { PropertyNameCaseInsensitive = true })); } public HttpResponse JsonMessage(string message, int statusCode = 200) { return new HttpResponse(statusCode) .WithContent(JsonContent.Create(new { Message = message })); } } ``` Entwickler können mit dieser Klasse Sitzungen, Kontexte und Datenbankverbindungen implementieren. Der bereitgestellte Code zeigt ein praktisches Beispiel mit dem `DatabaseConnectionHandler`, das die Freigabe der Datenbankverbindung am Ende jeder Anfrage automatisiert. Die Integration ist unkompliziert, da die Handler während der Server‑Einrichtung registriert werden. Die Klasse `HttpServerHandler` bietet ein leistungsstarkes Werkzeugset zum Ressourcen‑Management und zur Erweiterung des Sisk‑Verhaltens in HTTP‑Anwendungen. --- # Mehrere Listening-Hosts pro Server Source: https://docs.sisk-framework.org/de/docs/advanced/multi-host-setup.html Das Sisk Framework hat schon immer die Verwendung von mehr als einem Host pro Server unterstützt, das heißt, ein einzelner HTTP-Server kann auf mehreren Ports lauschen und jeder Port hat seinen eigenen Router und seinen eigenen Dienst, der darauf läuft. Auf diese Weise ist es einfach, Verantwortlichkeiten zu trennen und Dienste auf einem einzelnen HTTP-Server mit Sisk zu verwalten. Das nachstehende Beispiel zeigt die Erstellung von zwei ListeningHosts, die jeweils auf einem anderen Port lauschen, mit unterschiedlichen Routern und Aktionen. Lesen Sie [manually creating your app](https://docs.sisk-framework.org/de/docs/advanced/manual-setup.md), um die Details zu dieser Abstraktion zu verstehen. ```cs static void Main(string[] args) { // Erstelle zwei Listening-Hosts, von denen jeder seinen eigenen Router hat und // auf seinem eigenen Port lauscht // ListeningHost hostA = new ListeningHost(); hostA.Ports = [new ListeningPort(12000)]; hostA.Router = new Router(); hostA.Router.MapGet("/", request => new HttpResponse().WithContent("Hello from the host A!")); ListeningHost hostB = new ListeningHost(); hostB.Ports = [new ListeningPort(12001)]; hostB.Router = new Router(); hostB.Router.MapGet("/", request => new HttpResponse().WithContent("Hello from the host B!")); // Erstelle eine Serverkonfiguration und füge beide // Listening-Hosts hinzu // HttpServerConfiguration configuration = new HttpServerConfiguration(); configuration.ListeningHosts.Add(hostA); configuration.ListeningHosts.Add(hostB); // Erstellt einen HTTP-Server, der die angegebene // Konfiguration verwendet // HttpServer server = new HttpServer(configuration); // Startet den Server server.Start(); Console.WriteLine("Versuchen Sie, Host A unter {0} zu erreichen", server.ListeningPrefixes[0]); Console.WriteLine("Versuchen Sie, Host B unter {0} zu erreichen", server.ListeningPrefixes[1]); Thread.Sleep(-1); } ``` --- # HTTP-Server-Engines Source: https://docs.sisk-framework.org/de/docs/advanced/server-engines.html Das Sisk Framework ist in mehrere Pakete unterteilt, wobei das Hauptpaket (Sisk.HttpServer) keinen Basis-HTTP-Server enthält - standardmäßig wird [HttpListener](https://learn.microsoft.com/en-us/dotnet/api/system.net.httplistener?view=net-9.0) als Haupt-Engine von Sisk verwendet, um die untergeordnete Rolle des Servers auszuführen. Die HTTP-Engine erfüllt die Rolle der Schicht unterhalb der Anwendungsschicht, die von Sisk angeboten wird. Diese Schicht ist für die Verbindungsbearbeitung, die Serialisierung und Deserialisierung von Nachrichten, die Steuerung der Nachrichtenwarteschlange und die Kommunikation mit dem Socket des Computers verantwortlich. Die [HttpServerEngine](https://docs.sisk-framework.org/api/Sisk.Core.Http.Engine.HttpServerEngine.md)-Klasse bietet eine API, um alle notwendigen Funktionalitäten einer HTTP-Engine zu implementieren, die in höheren Schichten mit Sisk verwendet werden können, wie z.B. Routing, SSE, Middlewares usw. Diese Funktionen sind nicht die Verantwortung der HTTP-Engine, sondern vielmehr eines Teils der Bibliotheken, die die HTTP-Engine als Basis für die Ausführung verwenden. Durch diese Abstraktion ist es möglich, Sisk so anzupassen, dass es mit jeder anderen HTTP-Engine verwendet werden kann, die in .NET oder einer anderen Sprache geschrieben ist, wie z.B. Kestrel. Derzeit verwendet Sisk weiterhin eine Abstraktion des nativen .NET-[HttpListener](https://learn.microsoft.com/en-us/dotnet/api/system.net.httplistener?view=net-9.0) als Standard für neue Projekte. Diese Standardabstraktion bringt einige spezifische Probleme mit sich, wie z.B. unbestimmtes Verhalten auf verschiedenen Plattformen (HttpListener hat eine Implementierung für Windows und eine andere für andere Plattformen), fehlende Unterstützung für SSL und keine sehr angenehme Leistung außerhalb von Windows. Eine experimentelle Implementierung eines Hochleistungs-Servers, der rein in C# geschrieben ist, ist auch als HTTP-Engine für Sisk verfügbar, genannt das [Cadente](https://github.com/sisk-http/core/tree/main/cadente)-Projekt, das ein Experiment eines verwalteten Servers ist, der mit Sisk oder ohne verwendet werden kann. ## Implementierung einer HTTP-Engine für Sisk Sie können eine Verbindung zwischen einem bestehenden HTTP-Server und Sisk herstellen, indem Sie die [HttpServerEngine](https://docs.sisk-framework.org/api/Sisk.Core.Http.Engine.HttpServerEngine.md)-Klasse erweitern. Zusätzlich zu dieser Klasse müssen Sie auch Abstraktionen für Kontexte, Anfragen und Antworten implementieren. Ein vollständiges Abstraktionsbeispiel ist [auf GitHub](https://github.com/sisk-http/core/blob/main/src/Http/Engine/HttpListenerAbstractEngine.cs) verfügbar. Es sieht wie folgt aus: ```csharp /// /// Bietet eine Implementierung von mit . /// public sealed class HttpListenerAbstractEngine : HttpServerEngine { private HttpListener _listener; private static Lazy shared = new Lazy ( () => new HttpListenerAbstractEngine () ); /// /// Ruft die gemeinsam genutzte Instanz der -Klasse ab. /// public static HttpListenerAbstractEngine Shared => shared.Value; /// /// Initialisiert eine neue Instanz der -Klasse. /// public HttpListenerAbstractEngine () { _listener = new HttpListener { IgnoreWriteExceptions = true }; } /// public override TimeSpan IdleConnectionTimeout { get => _listener.TimeoutManager.IdleConnection; set => _listener.TimeoutManager.IdleConnection = value; } // ... } ``` ## Auswahl eines Ereignisschleifens Während der Erstellung einer HTTP-Engine wird der Server auf Anfragen hören und Kontexte erstellen, um jede davon in separaten Threads zu bearbeiten. Dazu müssen Sie einen [HttpServerEngineContextEventLoopMechanism](https://docs.sisk-framework.org/api/Sisk.Core.Http.Engine.HttpServerEngineContextEventLoopMechanism.md) auswählen: - `InlineAsynchronousGetContext` die Ereignisschleife ist linear - HTTP-Kontextbearbeitungsrufe erfolgen in einer asynchronen Schleife. - `UnboundAsynchronousGetContext` die Ereignisschleife wird durch die `BeginGetContext`- und `EndGetContext`-Methoden übertragen. ```csharp public override HttpServerEngineContextEventLoopMechanism EventLoopMechanism => HttpServerEngineContextEventLoopMechanism.UnboundAsynchronousGetContext; ``` Sie müssen nicht beide Ereignisschleifen implementieren. Wählen Sie diejenige, die am meisten Sinn für Ihre HTTP-Engine ergibt. ## Testen Nachdem Sie Ihre HTTP-Engine verknüpft haben, ist es wichtig, Tests durchzuführen, um sicherzustellen, dass alle Sisk-Funktionen identisches Verhalten aufweisen, wenn andere Engines verwendet werden. **Es ist extrem wichtig**, dass Sisk für verschiedene HTTP-Engines identisches Verhalten aufweist. Sie können das Test-Repository auf [GitHub](https://github.com/sisk-http/core/tree/main/tests) besuchen.