# 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, die dotnet‑CLI oder [weitere Optionen](https://www.nuget.org/packages/Sisk.HttpServer/) installieren. Richten Sie Ihre Sisk‑Umgebung ganz einfach ein, 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. Sisk 1.7 richtet sich an .NET 8, .NET 9 und .NET 10. Die [Cadente engine](https://docs.sisk-framework.org/de/docs/cadente.md)-Pakete zielen ausschließlich auf .NET 9 und .NET 10 ab. --- # 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 für die Produktion zu veröffentlichen. Obwohl der Vorgang relativ einfach ist, lohnt es sich, 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 fertig zu stellen. ## Veröffentlichen Ihrer Anwendung Das Veröffentlichen Ihrer Sisk-Anwendung oder Ihres Dienstes bedeutet, Binärdateien zu erzeugen, die für die Produktion bereit und optimiert sind. In diesem Beispiel werden wir die Binärdateien für die Produktion kompilieren, um sie auf einer Maschine auszuführen, auf der die .NET Runtime installiert ist. Sie benötigen das .NET SDK auf Ihrem Rechner, um Ihre Anwendung zu bauen, und die .NET Runtime auf dem Zielserver, um Ihre Anwendung auszuführen. Sie können erfahren, wie Sie die .NET Runtime 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 Sie den .NET‑Publish‑Befehl: ```shell $ dotnet publish -r linux-x64 -c Release ``` Damit werden Ihre Binärdateien im Verzeichnis `bin/Release/publish/linux-x64` erzeugt. > [!NOTE] > Wenn Ihre Anwendung eine [portable Konfigurationsdatei](https://docs.sisk-framework.org/de/docs/extensions/service-providers.md) verwendet, sollten Sie Ihre `service-config.json` zusammen mit allen von `dotnet publish` erzeugten Binärdateien auf Ihren Host‑Server kopieren. > Sie können die Datei vorkonfiguriert lassen, mit Umgebungsvariablen, Listening‑Ports und Hosts sowie zusätzlichen Serverkonfigurationen. Der nächste Schritt besteht darin, diese Dateien auf den Server zu übertragen, auf dem Ihre Anwendung gehostet wird. Danach geben Sie der Binärdatei Ausführungsrechte. In diesem Fall nehmen wir an, dass unser Projektname „my-app“ lautet: ```shell $ cd /home/htdocs $ chmod +x my-app $ ./my-app ``` Nachdem Sie Ihre Anwendung gestartet haben, prüfen Sie, ob Fehlermeldungen ausgegeben werden. Wenn keine erscheinen, läuft Ihre Anwendung. Zu diesem Zeitpunkt wird es wahrscheinlich nicht möglich sein, von außerhalb Ihres Servers auf Ihre Anwendung zuzugreifen, da Zugriffsregeln wie eine Firewall noch nicht konfiguriert sind. Wir werden dies in den nächsten Schritten berücksichtigen. Sie sollten die Adresse des virtuellen Hosts haben, an dem Ihre Anwendung lauscht. Diese wird manuell in der Anwendung festgelegt und hängt davon ab, wie Sie Ihren Sisk‑Dienst instanziieren. Wenn Sie **keine** portable Konfigurationsdatei verwenden, finden Sie sie dort, 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 ``` Manuelles Zuordnen eines ListeningHost: ```cs config.ListeningHosts.Add(new ListeningHost("https://localhost:5000/", router)); ``` Oder wenn Sie eine portable Konfigurationsdatei verwenden, in Ihrer service-config.json: ```json { "Server": { }, "ListeningHost": { "Ports": [ "http://localhost:5000/" ] } } ``` Daraus können wir einen Reverse‑Proxy erstellen, der Ihren Dienst abhört und den Datenverkehr über das offene Netzwerk verfügbar macht. ## Proxy Ihrer Anwendung Das Proxying Ihres Dienstes bedeutet, dass Sie Ihren Sisk‑Dienst nicht direkt einem externen Netzwerk aussetzen. Diese Praxis ist bei Server‑Deployments sehr verbreitet, weil: - Ermöglicht es Ihnen, ein SSL‑Zertifikat in Ihrer Anwendung zu verknüpfen; - Erstellen Sie Zugriffsregeln, bevor Sie den Dienst nutzen, und vermeiden Sie Überlastungen; - Steuern Sie Bandbreite und Anforderungsgrenzen; - Separate Load‑Balancer für Ihre Anwendung; - Verhindern Sie Sicherheitsbeschädigungen einer fehlerhaften Infrastruktur. Sie können Ihre Anwendung über 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. Denken Sie außerdem daran, die Weiterleitungs‑Header Ihres Proxys korrekt aufzulösen, um die Informationen Ihrer Clients, wie IP‑Adresse und Host, über [Forwarding‑Resolver](https://docs.sisk-framework.org/de/docs/advanced/forwarding-resolvers.md) zu erhalten. Der nächste Schritt nach dem Erstellen Ihres Tunnels, der Firewall‑Konfiguration und dem Betrieb Ihrer Anwendung besteht darin, einen Dienst für Ihre Anwendung zu erstellen. > [!NOTE] > Die direkte Verwendung von SSL‑Zertifikaten im Sisk‑Dienst auf Nicht‑Windows‑Systemen ist nicht möglich. Dies liegt an der Implementierung von HttpListener, dem zentralen Modul für die HTTP‑Queue‑Verwaltung in Sisk, und diese Implementierung variiert von Betriebssystem zu Betriebssystem. Sie können SSL in Ihrem Sisk‑Dienst verwenden, wenn Sie ein Zertifikat mit dem virtuellen Host über IIS [verknüpfen](https://learn.microsoft.com/en-us/iis/manage/configuring-security/how-to-set-up-ssl-on-iis). Für andere Systeme wird dringend empfohlen, einen Reverse‑Proxy zu nutzen. ## Erstellen eines Dienstes Das Erstellen eines Dienstes sorgt dafür, dass Ihre Anwendung stets verfügbar ist, selbst nach einem Neustart Ihrer Serverinstanz oder einem nicht wiederherstellbaren Absturz. In diesem einfachen Tutorial verwenden wir den Inhalt des vorherigen Tutorials als Beispiel, um Ihren Dienst dauerhaft aktiv zu halten. 1. Greifen Sie auf den Ordner zu, in dem sich die Service‑Konfigurationsdateien befinden: ```sh cd /etc/systemd/system ``` 2. Erstellen Sie Ihre Datei `my-app.service` und fügen Sie den Inhalt ein: ```ini {title="my-app.service"} [Unit] Description= [Service] # legt den Benutzer fest, der den Dienst startet User= # Der ExecStart-Pfad ist nicht relativ zum WorkingDirectory. # setze ihn als vollständigen Pfad zur ausführbaren Datei WorkingDirectory=/home/htdocs ExecStart=/home/htdocs/my-app # legt fest, dass der Dienst bei einem Absturz immer neu gestartet wird Restart=always RestartSec=3 [Install] WantedBy=multi-user.target ``` 3. Starten Sie Ihr Service‑Manager‑Modul neu: ```sh $ sudo systemctl daemon-reload ``` 4. Starten Sie den neu erstellten Dienst anhand des Dateinamens und prüfen Sie, ob er läuft: ```sh $ sudo systemctl start my-app $ sudo systemctl status my-app ``` 5. Wenn Ihre Anwendung jetzt läuft („Active: active“), aktivieren Sie Ihren Dienst, damit er nach einem Systemneustart weiterläuft: ```sh $ sudo systemctl enable my-app ``` Jetzt sind Sie bereit, 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 in den meisten Web‑Entwicklungsszenarien. Sisk basiert 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 verwenden, 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 nutzen 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 nutzen, um SSL auf Ihrem HTTP‑Server zu aktivieren. Damit dies funktioniert, sollten Sie vorher [dieses Tutorial](https://docs.sisk-framework.org/de/docs/registering-namespace.md) durchgehen, falls Ihre Anwendung auf einem anderen Host als „localhost“ lauschen soll. Dafür 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 [erfahren, wie Sie SSL unter IIS 7 oder höher einrichten](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 Interzeptions‑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, damit er am sicheren Port 8001 lauscht: ```sh mitmdump --mode reverse:http://localhost:8000/ -p 8001 ``` Und Sie sind startklar! 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. Dabei muss mitmproxy weiterhin auf Ihrem Rechner installiert sein. ## Sisk.SslProxy-Paket (eingestellt) > [!CAUTION] > > Das Sisk.SslProxy‑Paket ist eingestellt und auf NuGet veraltet. Es lässt sich nicht mehr gegen die aktuelle Cadente‑API kompilieren. Verwenden Sie es nicht: Nutzen Sie stattdessen die native SSL‑Unterstützung der [Cadente‑Engine](#through-the-siskcadentecoreengine). --- # 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 mehr Kontrolle und Flexibilität, insbesondere auf Nicht‑Windows‑Plattformen. ## Überblick Standardmäßig verwendet Sisk `HttpListener` (aus `System.Net`) als zugrunde liegende HTTP‑Server‑Engine. Während `HttpListener` unter Windows stabil und performant ist (wo es den Kernel‑Modus‑Treiber HTTP.sys nutzt), ist seine Implementierung unter Linux und macOS verwaltet und hatte historisch gesehen Einschränkungen, wie fehlende native SSL‑Unterstützung (die einen Reverse‑Proxy wie Nginx 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 Support:** Funktioniert auf allen Plattformen, ohne externe Proxies oder komplexe Konfiguration zu benötigen. - **Cross-Platform Consistency:** Identisches Verhalten unter Windows, Linux und macOS. - **Performance:** Entwickelt, um eine Hochleistungs‑Alternative zum verwalteten `HttpListener` zu sein. - **Independence:** Entkoppelt von `System.Net.HttpListener`, wodurch Sisk vor möglichen zukünftigen Deprecations oder fehlender Wartung dieser Komponente in .NET geschützt wird. > [!WARNING] > **Experimenteller Status** > > Cadente befindet sich derzeit in einer experimentellen Phase (Beta). Es wird noch nicht für kritische Produktionsumgebungen empfohlen. 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 Paket `Sisk.Cadente.CoreEngine`. ```bash dotnet add package Sisk.Cadente.CoreEngine ``` `Sisk.Cadente` und `Sisk.Cadente.CoreEngine` zielen auf .NET 9 und .NET 10 ab. Sie können nicht in Projekten verwendet werden, die .NET 8 anvisieren, obwohl `Sisk.HttpServer` dies unterstützt. ## Verwendung mit Sisk Um Cadente als HTTP‑Engine für Ihre Sisk‑Anwendung zu verwenden, müssen Sie den `HttpServer` so konfigurieren, dass er `CadenteHttpServerEngine` anstelle der Standard‑Engine nutzt. `CadenteHttpServerEngine` adaptiert den Cadente-`HttpHost` an die von Sisk benötigte `HttpServerEngine`‑Abstraktion. ```csharp using Sisk.Core.Http; using Sisk.Cadente.CoreEngine; using var host = HttpServer.CreateBuilder() .UseEngine() .UseSsl(certificate: CertificateHelper.CreateTrustedDevelopmentCertificate("localhost")) .Build(); await host.StartAsync(); ``` Wenn ein lauschernder Host mit SSL konfiguriert ist, gibt [HttpServer.ListeningPrefixes](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServer.ListeningPrefixes.md) seine Adressen mit dem `https`‑Schema aus. Sendet ein Client plain HTTP an einen mit SSL konfigurierten Port, antwortet Cadente mit einer `301 Moved Permanently`‑Weiterleitung zur `https`‑Adresse desselben Hosts und Pfads. ### Erweiterte Konfiguration Sie können die zugrunde liegende `HttpHost`‑Instanz anpassen, indem Sie dem Konstruktor von `CadenteHttpServerEngine` eine Setup‑Aktion übergeben. Dies ist nützlich, um Timeouts oder andere Low‑Level‑Einstellungen zu konfigurieren. ```csharp using var engine = new CadenteHttpServerEngine(host => { // Konfiguriere Lese-/Schreib-Timeouts des Clients host.TimeoutManager.ClientReadTimeout = TimeSpan.FromSeconds(30); host.TimeoutManager.ClientWriteTimeout = TimeSpan.FromSeconds(30); }); ``` ## Eigenständige Nutzung Obwohl Cadente hauptsächlich für Sisk konzipiert ist, kann es als eigenständiger HTTP‑Server verwendet werden (ähnlich wie `HttpListener`). ```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 stream = await context.Response.GetResponseStreamAsync(chunked: true); using var writer = new StreamWriter(stream); await writer.WriteLineAsync("Hello, world!"); } } ``` Wenn der Antwortkörper klein und bereits im Speicher ist, schreiben `WriteInlineContent` und `WriteInlineContentAsync` die Header und den Körper in einem einzigen Vorgang. Die Antwort muss einen `Content-Length`‑Header besitzen: ```csharp byte[] body = "Hello, world!"u8.ToArray(); context.Response.StatusCode = 200; context.Response.Headers.Set(new HttpHeader("Content-Length", body.Length.ToString())); await context.Response.WriteInlineContentAsync(body); ``` ## Anforderungsvalidierung Cadente prüft das Request‑Framing, bevor es den Handler erreicht. Seit Version 1.7 tut es Folgendes: - unterstützt pipelined Anfragen bei Keep‑Alive‑Verbindungen; - verwirft Anfragemethoden, die keine gültigen HTTP‑Tokens sind, und Anforderungsziele, die Bytes außerhalb des sichtbaren ASCII‑Bereichs (`%x21-7E`) enthalten; - verwirft Anfragen, deren `Transfer-Encoding` etwas anderes als einen einzelnen `chunked`‑Wert ist, sowie Anfragen mit widersprüchlichen `Content-Length`‑Headern; - antwortet mit `431 Request Header Fields Too Large` und schließt die Verbindung, wenn die Request‑Zeile und Header 8 KB überschreiten. Ungültige Anfragen werden mit einem Fehler beantwortet und die Verbindung wird geschlossen, ohne den Handler aufzurufen. --- # 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(); } } ``` --- # Änderungsprotokolle Source: https://docs.sisk-framework.org/de/docs/changelogs.html Jede Änderung an Sisk wird im Änderungsprotokoll festgehalten. Die Änderungsprotokolle für alle Sisk‑Versionen können Sie [hier](https://github.com/sisk-http/archive/tree/master/changelogs) einsehen. ## Migration von 1.6 zu 1.7 Sisk 1.7.0 liefert `Sisk.HttpServer`, `Sisk.Cadente`, `Sisk.Cadente.CoreEngine` und `Sisk.IniConfiguration` 1.7.0. `Sisk.Documenting`, `Sisk.Documenting.Html` und `Sisk.Monitoring` werden als 1.7.0-rc3 ausgeliefert, und `Sisk.ModelContextProtocol` als 1.7.0-rc1. Lesen Sie das [vollständige 1.7.0 Änderungsprotokoll](https://github.com/sisk-http/archive/blob/master/changelogs/core/v1.7/v1.7.0.md) für alle Änderungen. ### Breaking changes - **`Route.UseRegex` kann nicht mehr gesetzt werden.** Die Eigenschaft ist veraltet und gibt nur noch an, ob die Route ein `RegexRoute` ist. Erstellen Sie stattdessen ein [RegexRoute](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RegexRoute.md). Route‑Attribute mit `UseRegex = true` und das `[RegexRoute]`‑Attribut funktionieren weiterhin. Siehe [regex routes](https://docs.sisk-framework.org/de/docs/fundamentals/routing.md#regex-routes). ```cs // vorher router.Map(new Route(RouteMethod.Get, @"^/items/(?\d+)$", action) { UseRegex = true }); // danach router.Map(new RegexRoute(RouteMethod.Get, @"^/items/(?\d+)$", action)); ``` - **Anfragekörper sind standardmäßig auf 100 MB begrenzt.** Der Standardwert von [HttpServerConfiguration.MaximumContentLength](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.MaximumContentLength.md) hat sich von `0` (unbegrenzt) auf 100 MB geändert. Die Begrenzung gilt auch für Anforderungs‑Streams und für WebSocket‑Nachrichten. Setzen Sie ihn auf `0`, um das frühere Verhalten wiederherzustellen. Siehe [getting the request body](https://docs.sisk-framework.org/de/docs/fundamentals/requests.md#getting-the-request-body). ```cs using var app = HttpServer.CreateBuilder() .UseConfiguration(config => config.MaximumContentLength = 0) .Build(); ``` - **Statische Routen werden vor dynamischen Routen getestet.** Regex‑Routen, Präfix‑Routen (einschließlich Dateiservern) und benutzerdefinierte Matching‑Routen werden nach jeder statischen Route geprüft, unabhängig von ihrer Registrierungsreihenfolge. Siehe [matching precedence](https://docs.sisk-framework.org/de/docs/fundamentals/routing.md#matching-precedence). - **Doppelte Routennamen führen beim Start zu einer Ausnahme.** Zwei oder mehr Routen mit demselben `Name` verursachen eine `ArgumentException`, wenn der Server startet, während [Router.CheckForRouteCollisions](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.CheckForRouteCollisions.md) aktiviert ist (Standard). - **Dateiserver‑Routen sind Präfix‑Routen.** `Router.MapFileSystem` und `HttpFileServer.CreateServingRoute` erzeugen eine [PrefixRoute](https://docs.sisk-framework.org/api/Sisk.Core.Routing.PrefixRoute.md) anstelle einer Regex‑Route und werden nicht mehr von `ForceTrailingSlash` beeinflusst. - **`RouteMethod.Any` schließt `QUERY` ein.** Routen, die mit `MapAny` gemappt werden, beantworten ebenfalls `QUERY`‑Anfragen. Der numerische Wert von `RouteMethod.Any` hat sich geändert, daher muss Code, der ihn speichert oder vergleicht, neu kompiliert werden. - **CORS‑Preflight respektiert `UseCors`.** Die automatische `OPTIONS`‑Antwort sendet keine CORS‑Header mehr für Routen mit `UseCors = false`, und explizit für `OPTIONS` gemappte Routen bearbeiten die Anfrage stattdessen. Siehe [preflight requests](https://docs.sisk-framework.org/de/docs/features/cors.md#preflight-requests). - **Cadente lehnt fehlerhafte Anfragen ab.** Ungültige Anfragemethoden und -ziele, nicht unterstützte `Transfer-Encoding`‑Werte und widersprüchliche `Content-Length`‑Header werden abgelehnt, und Header‑Blöcke größer als 8 KB erhalten `431 Request Header Fields Too Large`. Siehe [Cadente request validation](https://docs.sisk-framework.org/de/docs/cadente.md#request-validation). ### Eingestellte Pakete `Sisk.BasicAuth`, `Sisk.SslProxy` und `Sisk.ServiceProvider` werden nicht mehr gepflegt und sind auf NuGet veraltet, ohne Ersatzpaket. Entfernen Sie sie aus Ihren Projekten: - Ersetzen Sie `Sisk.BasicAuth` durch Ihren eigenen [request handler](https://docs.sisk-framework.org/de/docs/fundamentals/request-handlers.md), der den `Authorization`‑Header validiert. - Ersetzen Sie `Sisk.SslProxy` durch die native SSL‑Unterstützung der [Cadente engine](https://docs.sisk-framework.org/de/docs/cadente.md). - Ersetzen Sie `Sisk.ServiceProvider` durch die [portable configuration](https://docs.sisk-framework.org/de/docs/extensions/service-providers.md), die in `Sisk.HttpServer` über `UsePortableConfiguration` integriert ist. ### Neue Funktionen - [Custom route matching](https://docs.sisk-framework.org/de/docs/fundamentals/routing.md#custom-route-matching) über `Route.Match`, `RouteMatch`, `Route.IsStatic` und `Route.AllowRewrites` sowie die neuen [prefix routes](https://docs.sisk-framework.org/de/docs/fundamentals/routing.md#prefix-routes). - Unterstützung der HTTP‑[QUERY‑Methode](https://docs.sisk-framework.org/de/docs/fundamentals/routing.md#query-method-routes) mit `MapQuery`, `Route.Query`, `[RouteQuery]` und dem Header `Accept-Query`. - [Atomic context values](https://docs.sisk-framework.org/de/docs/fundamentals/requests.md#atomic-context-values) über `HttpContext.Interlocked`. - Der `%{$key}`‑[access log term](https://docs.sisk-framework.org/de/docs/features/logging.md#access-log-formatting), der Werte aus dem Request‑Bag ausgibt, sowie die [safe file writing helpers](https://docs.sisk-framework.org/de/docs/features/logging.md#writing-files-safely) `LogStream.SafeWriteToFile` und `LogStream.SafeAppendToFile`. - In Cadente: pipelined Keep‑Alive‑Anfragen, `WriteInlineContent` und `WriteInlineContentAsync` sowie eine Weiterleitung zu HTTPS für reine HTTP‑Anfragen, die an einem SSL‑Port empfangen werden. - In den Erweiterungen: Parameterbeispiele und `LlmsApiExporter.FooterText` in [Sisk.Documenting](https://docs.sisk-framework.org/de/docs/extensions/api-documentation.md), Skills und die Protokollrevision `2026-07-28` in [Sisk.ModelContextProtocol](https://docs.sisk-framework.org/de/docs/extensions/mcp.md) sowie HTTP‑Traffic, Latenz, Routen‑ und Ausnahme‑Telemetry im Sisk.Monitoring‑Dashboard. ### Weitere Verhaltensänderungen - Die `HttpListener`‑Engine verteilt jede Anfrage an den Thread‑Pool, sodass eine langsame Anfrage die nächste akzeptierte Verbindung nicht mehr verzögert. Ihr `ContextAbortedToken` liefert `CancellationToken.None` zurück, anstatt eine Ausnahme zu werfen. - Ausnahmen, die von Server‑Handlern am Ende einer Anfrage bzw. beim Schreiben von Logs geworfen werden, werden in das Fehler‑Log geschrieben, anstatt an die HTTP‑Engine weiterzureichen. Siehe [exceptions thrown by handlers](https://docs.sisk-framework.org/de/docs/advanced/http-server-handlers.md#exceptions-thrown-by-handlers). - CORS‑Antworten senden `Vary: Origin`, wenn die erlaubte Herkunft von der Anfrage abhängt. - Der `%ri`‑Access‑Log‑Term verwendet die Client‑Adresse, die zu Beginn der Anfrage ermittelt wurde. - `Sisk.HttpServer` zielt auf .NET 8, .NET 9 und .NET 10. `Sisk.Cadente` und `Sisk.Cadente.CoreEngine` zielen auf .NET 9 und .NET 10 und veröffentlichen ihre Debug‑Symbole als `.snupkg`‑Pakete. --- # 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 beherbergen, die Endpunkte sind, die URLs und deren Methoden zu Aktionen abbilden, die vom Server ausgeführt werden. Jede Aktion ist dafür verantwortlich, eine Anfrage zu empfangen und eine Antwort an den Client zu liefern. Routen sind Paare aus 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(); // maps the GET / route into the following action 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 Funktionen, 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 von diesem Delegaten zurückgegebene Objekt 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 Abfragezeichenfolge zu berücksichtigen. Die erste Route, deren Pfad und Methode mit der Anfrage übereinstimmen, verarbeitet sie. Wenn keine Route mit dieser Anfrage übereinstimmt, wird die [Router.NotFoundErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.NotFoundErrorHandler.md)‑Antwort an den Client zurückgegeben. Wenn das Pfadmuster übereinstimmt, die HTTP‑Methode jedoch nicht, wird die [Router.MethodNotAllowedErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.MethodNotAllowedErrorHandler.md)‑Antwort an den Client gesendet. Die Reihenfolge, in der Routen getestet werden, ist in [matching precedence](#matching-precedence) beschrieben. Sisk prüft die Möglichkeit von Routenkollisionen zwischen statischen Routen. Beim Serverstart sucht Sisk nach statischen Routen, deren Pfadmuster und Methoden sich überschneiden, und wirft eine Ausnahme, wenn welche gefunden werden. In derselben Prüfung wirft Sisk eine `ArgumentException`, wenn zwei oder mehr Routen denselben [Route.Name](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Route.Name.md) teilen. Beide Prüfungen werden von [Router.CheckForRouteCollisions](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.CheckForRouteCollisions.md) gesteuert, das standardmäßig aktiviert ist. ### Matching precedence Jede Route ist entweder **statisch** oder **dynamisch**, wie durch [Route.IsStatic](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Route.IsStatic.md) angezeigt: - **Statische Routen** folgen strikt ihrem Pfadmuster, z. B. `/users/`. Mit `Map*`‑Methoden erstellte Routen, `Route`‑Instanzen und Routen‑Attribute sind statisch. - **Dynamische Routen** interpretieren ihren Pfad auf eigene Weise und können Pfade abgleichen, die nicht durch ein Pfadmuster beschrieben werden können. [RegexRoute](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RegexRoute.md), [PrefixRoute](https://docs.sisk-framework.org/api/Sisk.Core.Routing.PrefixRoute.md) (verwendet von [file servers](https://docs.sisk-framework.org/de/docs/features/file-server.md)) und [custom matching routes](#custom-route-matching) sind dynamisch. Wenn der Server startet, ordnet der Router seine Routen einmal neu, sodass jede statische Route vor jeder dynamischen Route getestet wird. Innerhalb jeder Gruppe behalten die Routen ihre Registrierungsreihenfolge bei. Das bedeutet, dass eine statische Route immer Vorrang vor einer dynamischen Route hat, unabhängig von der Reihenfolge, in der sie definiert wurden: ```cs // dynamic: serves any GET path from the "wwwroot" directory mainRouter.MapFileSystem("/", "wwwroot"); // static: handles GET /api/status, even though it was defined after the file server mainRouter.MapGet("/api/status", request => new HttpResponse("ok")); ``` Im obigen Beispiel fungiert der Dateiserver als Rückfallback für jede GET‑Anfrage, die nicht von einer statischen Route verarbeitet wird. Die beiden Gruppen unterscheiden sich außerdem darin, wie sie validiert und transformiert werden: | | Statische Routen | Dynamische Routen | |---------------------|------------------|-------------------| | Matching order | Zuerst, in Registrierungsreihenfolge | Nach allen statischen Routen, in Registrierungsreihenfolge | | Route collision check | Ja | Nein | | [Router.Prefix](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.Prefix.md) wird angewendet | Ja | Nein | > [!WARNING] > Dynamische Routen werden nicht auf Kollisionen geprüft, da Sisk nicht wissen kann, welche Pfade sie matchen. Wenn zwei dynamische Routen dieselbe Anfrage matchen können, gewinnt die zuerst definierte. Zum Beispiel macht ein Dateiserver, der auf `/` gemappt ist und vor einem Dateiserver, der auf `/static` gemappt ist, definiert wird, den zweiten unerreichbar. > [!IMPORTANT] > In Versionen vor 1.7 wurden Routen nur in ihrer Registrierungsreihenfolge getestet, und Regex‑Routen konnten Vorrang vor statischen Routen haben, die danach definiert wurden. Wenn Ihre Anwendung darauf angewiesen ist, dass eine Regex‑Route einen Pfad verarbeitet, der auch von einer statischen Route gematcht wird, verschieben Sie diese Logik in die statische Route oder schränken Sie das statische Pfadmuster ein. ### Creating routes using path patterns Für neue Anwendungen sollten Sie die `Map*`‑Methoden bevorzugen. Sie halten die HTTP‑Methode an der Aufrufstelle 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* methods are the usual way to define method-specific routes. 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(); // empty 200 ok }); // Map can also receive a Route instance when you need route options. mainRouter.Map(Route.Get("/image.png", (request) => { var imageStream = File.OpenRead("image.png"); return new HttpResponse() { // the StreamContent inner // stream is disposed after sending // the response. Content = new StreamContent(imageStream) }; })); // multiple parameters 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 vor dem Test des Pfadmusters normalisiert, nach folgenden Regeln: - Alle leeren Segmente werden aus dem Pfad entfernt, z. B.: `////foo//bar` wird zu `/foo/bar`. - Die Pfad‑Übereinstimmung 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 nachstehende Beispiel liest den Routenparameter „id“ und erhält daraus ein `Guid`. Ist der Parameter kein gültiges Guid, wird eine Ausnahme geworfen, und ein 500‑Fehler wird an den Client zurückgegeben, wenn der Server nicht [Router.CallbackErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.CallbackErrorHandler.md) behandelt. ```cs mainRouter.MapGet("/user/", (request) => { Guid id = request.RouteParameters["id"].GetGuid(); return new HttpResponse($"User id: {id}"); }); ``` > [!NOTE] > Pfadenden `/` werden sowohl in Anfragen als auch in Routenpfaden ignoriert, das heißt, wenn Sie versuchen, auf eine Route zuzugreifen, die als `/index/page` definiert ist, können Sie sie auch mit `/index/page/` erreichen. > > Sie können URLs auch 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 mittels Reflexion mit dem Attribut [RouteAttribute](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouteAttribute.md) definieren. Auf diese Weise werden die Instanzen einer Klasse, deren Methoden dieses Attribut implementieren, im Ziel‑Router als Routen definiert. Damit eine Methode als Route definiert wird, muss sie mit einem [RouteAttribute] markiert sein, etwa dem Attribut selbst oder einem [RouteGetAttribute](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouteGetAttribute.md). Die Methode kann statisch, instanziiert, öffentlich oder privat sein. Verwenden Sie `MapInstance`, wenn Sie Instanz‑ und statische Routenmethoden aus einem Objekt zuordnen möchten. Verwenden Sie `MapType`, wenn Sie nur statische Routenmethoden aus einem Typ zuordnen möchten. ```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 folgende Zeile 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 ihr Typ übergeben worden, würden nur die statischen Methoden definiert werden. ```cs var myController = new MyController(); mainRouter.MapInstance(myController); ``` Um nur statische Routenmethoden aus einem Typ 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(); ``` Die obige Anweisung sucht nach allen Typen, die `ApiController` implementieren, jedoch **nicht den Typ selbst**. Die beiden optionalen Parameter geben an, wie die Methode nach diesen Typen sucht. Das erste Argument bezeichnet die Assembly, in der nach den Typen gesucht wird, und das zweite gibt an, wie die Typen definiert werden. ## Regex routes Anstatt die standardmäßigen HTTP‑Pfad‑Matching‑Methoden zu verwenden, können Sie eine Route definieren, deren Pfad als regulärer Ausdruck mit der Klasse [RegexRoute](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RegexRoute.md) interpretiert wird. Regex‑Routen sind [dynamische Routen](#matching-precedence). ```cs mainRouter.Map(new RegexRoute(RouteMethod.Get, @"\/[a-z]+\/", request => { return new HttpResponse("hello, world"); })); ``` Sie können auch Gruppen aus dem Regex‑Muster in den Inhalt von [HttpRequest.RouteParameters](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.RouteParameters.md) erfassen: ```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}"); } } ``` > [!NOTE] > Der reguläre Ausdruck ist nicht automatisch verankert: ein Muster wie `/uploads` matcht auch `/files/uploads/a`. Verwenden Sie `^` und `$`, um den gesamten Pfad zu matchen. > [!IMPORTANT] > Seit Sisk 1.7 kann die Eigenschaft `Route.UseRegex` nicht mehr gesetzt werden. Sie ist veraltet und gibt nur noch an, ob die Route eine [RegexRoute](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RegexRoute.md) ist. Die `UseRegex`‑Eigenschaft von Routen‑Attributen, wie `[Route(RouteMethod.Get, "...", UseRegex = true)]`, und das `[RegexRoute]`‑Attribut funktionieren weiterhin. Um Code zu migrieren, der Regex‑Routen mit `Route.UseRegex` erstellt hat, ersetzen Sie die Route durch eine `RegexRoute`: ```cs // before 1.7 mainRouter.Map(new Route(RouteMethod.Get, @"^/items/(?\d+)$", action) { UseRegex = true }); // 1.7 and later mainRouter.Map(new RegexRoute(RouteMethod.Get, @"^/items/(?\d+)$", action)); ``` ## Prefix routes Eine [PrefixRoute](https://docs.sisk-framework.org/api/Sisk.Core.Routing.PrefixRoute.md) stimmt mit ihrem Pfad und jedem darunter liegenden Pfad überein, indem ganze Pfadsegmente verglichen werden. Sie ist die von [Router.MapFileSystem](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.MapFileSystem.md) verwendete Route. Präfix‑Routen sind [dynamische Routen](#matching-precedence). ```cs // stimmt mit /docs, /docs/ und /docs/any/nested/path überein, aber nicht mit /docsfoo mainRouter.Map(new PrefixRoute(RouteMethod.Get, "/docs", null, request => { return new HttpResponse($"Requested {request.Path}"); }, null)); ``` ## Custom route matching Sie können eigene Matching‑Regeln erstellen, indem Sie von [Route](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Route.md) erben und [Route.Match](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Route.Match.md) überschreiben. Die Methode erhält den Anfrage‑Pfad und den Router, der die Route abgleicht, und gibt ein [RouteMatch](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouteMatch.md) mit dem Ergebnis und optional den erfassten Routenparametern zurück. Der Router prüft die HTTP‑Methode erst, nachdem `Match` erfolgreich war, daher sollte `Match` nur den Pfad testen. Dadurch funktionieren die [405 Method Not Allowed](#method-not-allowed-405-callback-handler) und `OPTIONS`‑Antworten für benutzerdefinierte Routen. ```cs {title="Routing/ExtensionRoute.cs"} // passt zu jedem Pfad, der mit der angegebenen Dateierweiterung endet, z. B. /reports/2024.csv public sealed class ExtensionRoute : Route { public ExtensionRoute(string extension, RouteAction action) : base(RouteMethod.Get, extension, action) { } // diese Route folgt keinem Pfadmuster public override bool IsStatic => false; // der Pfad ist kein Pfadmuster, daher kann er nicht auf seine Trailing‑Slash‑Form umgeleitet werden public override bool AllowRewrites => false; public override RouteMatch Match(string requestPath, Router router) { StringComparison comparison = router.MatchRoutesIgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; if (!requestPath.EndsWith(Path, comparison)) return RouteMatch.NotMatched; var parameters = new NameValueCollection { ["file"] = requestPath[..^Path.Length] }; return new RouteMatch(true, parameters); } } ``` ```cs mainRouter.Map(new ExtensionRoute(".csv", request => { string file = request.RouteParameters["file"].GetString(); return new HttpResponse($"Exporting {file} as CSV"); })); ``` Beim Überschreiben von `Match` sollten Sie Folgendes beachten: - Überschreiben Sie [Route.IsStatic](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Route.IsStatic.md), um `false` zurückzugeben, wann immer die Route nicht strikt einem Pfadmuster folgt. Andernfalls behandelt der Router ihr `Path` als Pfadmuster bei der Kollisionsprüfung und beim Anwenden von [Router.Prefix](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.Prefix.md), was zu falschen Kollisionen oder ungültigen Pfaden führen kann. - Überschreiben Sie [Route.AllowRewrites](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Route.AllowRewrites.md), um `false` zurückzugeben, wenn der Routenpfad nicht durch [HttpServerConfiguration.ForceTrailingSlash](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ForceTrailingSlash.md) auf seine Trailing‑Slash‑Form umgeleitet werden kann. - `Match` wird für jede Anfrage aufgerufen, die die Route erreicht, möglicherweise gleichzeitig von mehreren Threads. Vermeiden Sie Allokationen, wenn die Route nicht passt, und halten Sie jeglichen zwischengespeicherten Zustand thread‑sicher. Geben Sie [RouteMatch.NotMatched](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouteMatch.NotMatched.md) für erfolglose Übereinstimmungen zurück. - Berücksichtigen Sie [Router.MatchRoutesIgnoreCase](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.MatchRoutesIgnoreCase.md) über den Parameter `router`. ## Prefixing routes Sie können allen Routen in einer Klasse oder einem Modul mit dem Attribut [RoutePrefix](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RoutePrefixAttribute.md) einen Präfix hinzufügen und den Präfix als Zeichenkette festlegen. Siehe das nachstehende 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 Verwendung über den globalen Kontext [HttpContext.Current](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpContext.Current.md). Lesen Sie mehr 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 ist es möglich, die Anfrage und ihre Komponenten im Anforderungskontext zu erhalten. Betrachten wir eine Abstraktion `ControllerBase`, die als Grundlage für alle Controller einer API dient und die `Request`‑Eigenschaft bereitstellt, um den aktuellsten [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‑Sitzung ab, oder erstellt eine neue, falls sie nicht existiert public DbContext Database { get => HttpContext.Current.RequestBag.GetOrAdd(); } } ``` Und damit alle Nachfolger sie verwenden können, ohne den Anfrage‑Parameter: ```cs {title="Controller/UsersController.cs"} [RoutePrefix("/api/users")] public class UsersController : ControllerBase { [RoutePost] public async Task Create() { // reads the JSON data from the current request 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 [dependency injection](https://docs.sisk-framework.org/de/docs/features/instancing.md)‑Tutorial. ## Any method routes Sie können eine Route definieren, die nur anhand ihres Pfads abgeglichen wird und die HTTP‑Methode überspringt. Das kann nützlich sein, um die Methodenvalidierung innerhalb des Routen‑Callbacks durchzuführen. ```cs // will match / on any HTTP method mainRouter.MapAny("/", callbackFunction); ``` [RouteMethod.Any](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouteMethod.md) umfasst `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `QUERY`, `HEAD` und `OPTIONS`. ## QUERY method routes Sisk unterstützt die HTTP‑Methode `QUERY`, eine sichere und idempotente Methode, die die Abfrage im Anfrage‑Body überträgt. Verwenden Sie [Router.MapQuery](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.MapQuery.md), [Route.Query](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Route.Query.md), [RouteMethod.Query](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouteMethod.md) oder das [RouteQuery](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouteQueryAttribute.md)‑Attribut, um diese Routen zu definieren: ```cs mainRouter.MapQuery("/search", request => { string filter = request.Body; return new HttpResponse($"Searching for {filter}"); }); ``` ```cs {title="Controller/SearchController.cs"} public class SearchController { [RouteQuery("/products")] static HttpResponse SearchProducts(HttpRequest request) { return new HttpResponse($"Searching for {request.Body}"); } } ``` Der Header `Accept-Query`, der die in `QUERY`‑Anfragen akzeptierten Medientypen ankündigt, ist über [HttpHeaderCollection.AcceptQuery](https://docs.sisk-framework.org/api/Sisk.Core.Entity.HttpHeaderCollection.AcceptQuery.md) und [HttpKnownHeaderNames.AcceptQuery](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpKnownHeaderNames.AcceptQuery.md) verfügbar. ## Any path routes Routen für beliebige Pfade prüfen jeden vom HTTP‑Server empfangenen Pfad, abhängig von der getesteten Routen‑Methode. Wenn die Routen‑Methode RouteMethod.Any ist und die Route [Route.AnyPath](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Route.AnyPath.md) in ihrem Pfadausdruck verwendet, hört diese Route auf alle Anfragen des HTTP‑Servers, und es können keine weiteren Routen definiert werden. ```cs // the following route will match all POST requests mainRouter.Map(RouteMethod.Post, Route.AnyPath, callbackFunction); ``` ## Ignore case route matching Standardmäßig ist die Interpretation von Routen mit Anfragen case‑sensitive. Um die Groß‑/Kleinschreibung zu ignorieren, aktivieren Sie diese Option: ```cs mainRouter.MatchRoutesIgnoreCase = true; ``` Dies aktiviert außerdem die Option `RegexOptions.IgnoreCase` für Regex‑Routen und den case‑insensitiven Vergleich für Präfix‑Routen. ## Not Found (404) callback handler Sie können einen benutzerdefinierten Callback erstellen, wenn eine Anfrage keine bekannte Route trifft. ```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 auch einen benutzerdefinierten Callback erstellen, wenn eine Anfrage mit ihrem Pfad übereinstimmt, aber nicht mit der Methode. ```cs mainRouter.MethodNotAllowedErrorHandler = (context) => { return new HttpResponse(405) { Content = new StringContent($"Method not allowed for this route.") }; }; ``` ## Error Handling Ausnahmen können innerhalb des Anforderungs‑Lebenszyklus ausgelöst 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 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, und der HTTP‑Server kann unterbrochen werden, wenn die Ausnahme nicht gefangen wird. - Wenn [HttpServerConfiguration.ThrowExceptions](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ThrowExceptions.md) `false` ist, werden Ausnahmen von Sisk abgefangen und behandelt. Danach, falls `Router.CallbackErrorHandler` definiert ist, wird er mit der abgefangenen Ausnahme und dem Anforderungskontext aufgerufen, und er **wird 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 keinen festlegen und das Standard‑Log‑Schreibverhalten ändern: ```csharp router.CallbackErrorHandler = (ex, ctx) => { ctx.LogMode = LogOutput.Both; // override log mode to log the error in both access and error logs } ``` ## Internal error handler Routen‑Callbacks können während der Serverausführung Fehler werfen. Wenn sie nicht korrekt behandelt werden, kann die Gesamtfunktion des HTTP‑Servers beendet werden. Der Router verfügt über einen Callback für den Fall, dass ein Routen‑Callback fehlschlägt und eine Service‑Unterbrechung 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, sowie zum Speichern von Daten, Protokollierung oder anderen Schritten, die vor oder nach einer Antwort ausgeführt werden können. 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 verworfen wird. Beispiel: Angenommen, ein Request-Handler zur Benutzer‑Authentifizierung authentifiziert den Nutzer nicht. Er verhindert, dass der Anfragen‑Lebenszyklus fortgesetzt wird, und lässt die Verarbeitung hängen. Wenn dies im Request-Handler an zweiter Stelle geschieht, werden der dritte und alle nachfolgenden nicht mehr ausgewertet. ![](https://docs.sisk-framework.org/assets/img/requesthandlers1.png) ## Erstellen eines Request‑Handlers Um einen Request‑Handler zu erstellen, können wir eine Klasse anlegen, die das [IRequestHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.IRequestHandler.md)-Interface implementiert, 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 Anfragenzyklus 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 und der nächste Request‑Handler oder der Router‑Callback aufgerufen werden soll, je nachdem, was als Nächstes kommt. Wird ein Request‑Handler nach der Antwort ausgeführt (über die Eigenschaft [ExecutionMode](https://docs.sisk-framework.org/api/Sisk.Core.Routing.IRequestHandler.ExecutionMode.md)) und gibt einen von null verschiedenen Wert zurück, überschreibt er die Antwort des Routers. Gibt ein Request‑Handler `null` zurück, bedeutet das, dass die Anfrage fortgesetzt werden muss und das nächste Objekt aufgerufen wird bzw. der Zyklus mit der Antwort des Routers endet. > [!WARNING] > Eine Request‑Handler‑Instanz wird einmal erstellt und von allen Anfragen, die sie verarbeitet, gemeinsam genutzt, einschließlich gleichzeitiger Anfragen. **Speichern Sie niemals zustandsbezogene Daten einer einzelnen Anfrage in der Request‑Handler‑Klasse selbst** (Felder oder Eigenschaften wie der aktuelle Benutzer, ein geparstes Token oder ein Timer), da dieser Wert in andere Anfragen „leckt“. Speichern Sie anfragebezogene Werte stattdessen im [RequestBag](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpContext.RequestBag.md) des `HttpContext`: > > ```cs > public HttpResponse? Execute(HttpRequest request, HttpContext context) > { > // falsch: this.currentUser = FindUser(request); > context.RequestBag.Set(FindUser(request)); > return null; > } > ``` 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 einer Route einen oder mehrere Request‑Handler zuweisen. ```cs {title="Router.cs"} mainRouter.Map(RouteMethod.Get, "/", IndexPage, new IRequestHandler[] { new AuthenticateUserRequestHandler(), // Request‑Handler vor der Anfrage new ValidateJsonContentRequestHandler(), // Request‑Handler vor der Anfrage // -- Methode IndexPage wird hier ausgeführt new WriteToLogRequestHandler() // Request‑Handler nach der Anfrage }); ``` Oder beim Erstellen eines [Route](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Route.md)-Objekts: ```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 so: ```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: dieselbe Instanz wie in den globalen Request‑Handlern new AuthenticateUserRequestHandler() // falsch: wird den globalen Request‑Handler nicht überspringen }; 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 angegeben ist. --- # 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 zur Verarbeitung von HTTP-Nachrichten in Ihrer Anwendung. Eine HTTP-Anfrage besteht aus der Methode, dem Pfad, der Version, den Headern und dem 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 Client-Adresse 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 aus `Accept-Language` ermittelte beste 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 die konfigurierte HTTP‑Engine dies 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 // ruft den Anfragetext als Zeichenkette ab, 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 lese den Body asynchron Memory bodyMemory = await request.GetBodyContentsAsync(); ``` Es ist außerdem möglich, mit den Properties [HasContents](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.HasContents.md) und [IsContentAvailable](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.IsContentAvailable.md) zu bestimmen, ob ein Body vorhanden ist und ob er geladen wurde. Es ist nicht möglich, den Anforderungsinhalt über `GetRequestStream` mehr als einmal zu lesen. Wenn Sie mit dieser Methode lesen, 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 entsorgen, da er am Ende der HTTP‑Sitzung, in der er erstellt wurde, freigegeben wird. Außerdem können Sie die Property [HttpRequest.RequestEncoding](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.RequestEncoding.md) verwenden, um die beste Codierung zum manuellen Dekodieren der Anfrage zu erhalten. Der Server hat Beschränkungen für das 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). Der Anforderungsinhalt ist durch [HttpServerConfiguration.MaximumContentLength](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.MaximumContentLength.md) begrenzt, das seit Sisk 1.7 standardmäßig 100 MB beträgt (zuvor war es `0`, unbegrenzt). Das Limit gilt wie folgt: - Eine Anfrage, die einen `Content-Length` größer als das Limit angibt, erhält eine `413 Content Too Large`‑Antwort, bevor die Route aufgerufen wird. - Ein chunked‑ oder gestreamter Body ohne deklarierte Länge wird während des Lesens geprüft. Das Überschreiten des Limits über [RawBody](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.RawBody.md), [Body](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Body.md) oder den von [GetRequestStream](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.GetRequestStream.md) zurückgegebenen Stream wirft eine [InsufficientMemoryException](https://learn.microsoft.com/en-us/dotnet/api/system.insufficientmemoryexception), was zu einem `500 Internal Server Error` führt, sofern Ihr Code oder [Router.CallbackErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.CallbackErrorHandler.md) dies nicht abfängt. - Das gleiche Limit ist die maximale Größe einer empfangenen [WebSocket](https://docs.sisk-framework.org/de/docs/features/websockets.md) Nachricht. Setzen Sie die Property auf `0`, um das Limit zu entfernen: ```cs using var app = HttpServer.CreateBuilder() .UseConfiguration(config => config.MaximumContentLength = 0) .Build(); ``` Selbst ohne konfiguriertes Limit können `Body` und `RawBody` keinen Inhalt größer als [Array.MaxLength](https://learn.microsoft.com/en-us/dotnet/api/system.array.maxlength) (etwa 2 GB) puffern und werfen eine Ausnahme, wenn Sie versuchen, darauf zuzugreifen. Größere Inhalte können weiterhin über Streaming verarbeitet werden. > [!NOTE] > Obwohl Sisk es erlaubt, ist es stets ratsam, den HTTP‑Semantiken zu folgen, um Ihre Anwendung zu erstellen und keinen Inhalt in Methoden zu erhalten oder zu liefern, die dies nicht zulassen. Lesen Sie ü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‑Unterstützung die Deserialisierung stoppen soll: ```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 für einen bestimmten Endpunkt benutzerdefinierte [JsonSerializerOptions](https://learn.microsoft.com/en-us/dotnet/api/system.text.json.jsonserializeroptions) ü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; ``` ### Request‑Bag 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 Routencallback ausgeführt werden. > [!TIP] > Diese Eigenschaft ist auch über die [HttpRequest.Bag](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Bag.md) Eigenschaft 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 ihres Typs zu setzen bzw. zu holen. Die Klasse `TypedValueDictionary` bietet ebenfalls die Methoden `GetValue` und `SetValue` für mehr Kontrolle. ```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(); ... } ``` ### Atomare Kontextwerte Der Request‑Bag ist nicht synchronisiert. Wenn numerische Werte im Bag während derselben Anfrage von konkurrierendem Code geändert werden können, etwa durch parallele Tasks, die von der Route gestartet werden, verwenden Sie [HttpContext.Interlocked](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpContext.Interlocked.md). Sie gibt ein [HttpContextInterlocked](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpContext.HttpContextInterlocked.md) Objekt mit atomaren Operationen über benannte Werte des Request‑Bags zurück: | Methode | Beschreibung | | --- | --- | | `Add(name, value)` | Addiert `value` zum gespeicherten Wert oder speichert `value`, wenn `name` nicht gesetzt ist. | | `Exchange(name, value)` | Ersetzt den gespeicherten Wert und gibt den vorherigen zurück, oder `value`, wenn `name` nicht gesetzt war. | | `CompareExchange(name, value, comparand, notFound = -1)` | Ersetzt den gespeicherten Wert durch `value` nur, wenn er dem `comparand` entspricht, und gibt den ursprünglichen Wert zurück, oder `notFound`, wenn `name` nicht gesetzt ist. | | `Inspect(name)` | Gibt den gespeicherten Wert zurück, oder `null`, wenn `name` nicht gesetzt ist. | Werte werden als `double` im Request‑Bag gespeichert. Überladungen, die `int` oder `long` erhalten, konvertieren sie zu `double`. ```cs router.MapPost("/import", async (HttpRequest request) => { string[] lines = request.Body.Split('\n'); await Parallel.ForEachAsync(lines, async (line, cancellation) => { if (await TryImportAsync(line, cancellation)) request.Context.Interlocked.Add("imported", 1); }); double imported = request.Context.Interlocked.Inspect("imported") ?? 0; return new HttpResponse($"{imported} line(s) imported"); }); ``` > [!NOTE] > Die Operationen sind nur gegenüber anderen `Interlocked`‑Aufrufen atomar. Das Lesen oder Schreiben desselben Schlüssels direkt über `RequestBag` ist nicht mit ihnen synchronisiert. ## Abrufen von Formulardaten Sie können Formulardatenwerte in einer [StringKeyStoreCollection](https://docs.sisk-framework.org/api/Sisk.Core.Entity.StringKeyStoreCollection.md) wie im folgenden 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, Formularfelder 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 unten `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‑Objekten](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) einen Abbruch‑Token bereit. Wenn die konfigurierte HTTP‑Engine die Erkennung von Disconnects unterstützt, wird dieser 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) => { // bekommt den Disconnect‑Token aus der Anfrage var dc = req.DisconnectToken; await LongOperationAsync(dc); return new HttpResponse(); }); ``` Dieser 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 verwendet, ist `DisconnectToken` `CancellationToken.None`; praktisch handelt es sich um einen nicht abbruchfähigen Token, der 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. Auch bei einer unterstützten Engine ist das Abbrechen kooperativ: Geben Sie den Token an asynchrone APIs weiter und prüfen Sie ihn in Ihrer eigenen langlaufenden Arbeit. ## Unterstützung von Server‑Sent Events Sisk unterstützt [Server‑Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events), wodurch Datenblöcke als Stream gesendet und die Verbindung zwischen Server und Client aufrechterhalten werden kann. Der Aufruf der Methode [HttpRequest.GetEventSource](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.GetEventSource.md) versetzt das HttpRequest in den Listener‑Zustand. In diesem Zustand erwartet der Kontext dieser HTTP‑Anfrage keine HttpResponse, da sie die von Server‑Side‑Events gesendeten Pakete überlagern würde. Nachdem alle Pakete gesendet wurden, muss der Callback die [Close](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequestEventSource.Close.md)‑Methode 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 einen `Content‑Length`‑Header bestimmt werden kann. Nach den Standardeinstellungen der meisten Browser unterstützen Server‑Side‑Events das Senden von HTTP‑Headern oder Methoden außer 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/Web/API/EventSource/close) auf der Client‑Seite nach Empfang aller Pakete nicht aufgerufen wird, was zu einer unendlichen zusätzlichen Verarbeitung auf 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 das fortlaufende Senden der Nachrichten an den Client: ```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 diesem ähnelt: ## Auflösen von proxied IPs und Hosts Sisk kann mit Proxies verwendet werden, sodass IP‑Adressen durch den Proxy‑Endpunkt in der Transaktion vom Client zum Proxy 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‑Kodierung Header‑Kodierung kann bei einigen Implementierungen problematisch sein. Unter Windows werden UTF‑8‑Header nicht unterstützt, daher wird ASCII verwendet. Sisk verfügt über einen integrierten Kodierungskonverter, der beim Dekodieren falsch kodierter 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 protokolliert werden. Es ist möglich, Log‑Rotation, Erweiterungen und Frequenz zu definieren. Die Klasse [LogStream](https://docs.sisk-framework.org/api/Sisk.Core.Http.LogStream.md) bietet eine asynchrone Methode zum Schreiben von Logs und hält sie in einer abfragbaren 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 das Logging für Ihre Anwendung konfigurieren. ## Dateibasierte Zugriffslogs Logs zu Dateien öffnen die Datei, schreiben den Zeilentext und schließen die Datei anschließend für jede geschriebene Zeile. Dieses Verfahren wurde gewählt, um die Schreib‑Reaktionsfähigkeit der 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 erzeugt. ## Stream‑basiertes Logging Sie können Log‑Dateien in `TextWriter`‑Objekte 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 Zugriffslogs Sie können das Zugriffslog‑Format 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 erzeugt eine Meldung wie: 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 (als zweistellige Zahl formatiert) | 05 | | %dmmm | Vollständiger Name des Monats | July | | %dmm | Abgekürzter Name des Monats (drei Buchstaben) | Jul | | %dm | Monatszahl (als zweistellige Zahl formatiert) | 07 | | %dy | Jahr (vierstellig) | 2023 | | %th | Stunde im 12‑Stunden‑Format | 03 | | %tH | Stunde im 24‑Stunden‑Format (HH) | 15 | | %ti | Minuten (zweistellig) | 30 | | %ts | Sekunden (zweistellig) | 45 | | %tm | Millisekunden (dreistellig) | 123 | | %tz | Zeitzonenoffset (Gesamtstunden in UTC) | +03:00 | | %ri | Remote‑IP‑Adresse des Clients, ermittelt zu Beginn der Anfrage | 192.168.1.100 | | %rm | HTTP‑Methode (Großbuchstaben) | 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` | | %{$key} | Stellt den unter `key` gespeicherten Wert in [HttpContext.RequestBag] dar, konvertiert mit `ToString()`. Leer, wenn der Schlüssel nicht gesetzt ist. | `42` | Sie können außerdem `HttpServerConfiguration.DefaultAccessLogFormat` verwenden, um das Standard‑Zugriffslog‑Format zu nutzen. Der Ausdruck `%{$key}` ermöglicht es Request‑Handlern und Routen, eigene Werte zum Zugriffslog hinzuzufügen: ```cs config.AccessLogsFormat = "%dd/%dmm/%dy %tH:%ti:%ts %rm %rz [%sc] user=%{$UserId}"; router.MapGet("/profile", request => { request.Bag["UserId"] = "42"; return new HttpResponse("ok"); }); ``` ## Rotierende Logs Sie können den HTTP‑Server so konfigurieren, dass Log‑Dateien in eine komprimierte *.gz*-Datei rotiert werden, sobald sie eine bestimmte Größe erreichen. Die Größe wird periodisch anhand des von Ihnen definierten Schwellenwerts 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 `LogStream` die Grenze von 64 MiB 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 Kompression abgeschlossen und die Datei bereinigt ist. Alle Zeilen, die in diesem Zeitraum geschrieben werden sollen, landen in einer Warteschlange, die auf das Ende der Kompression wartet. Diese Funktion arbeitet ausschließlich mit dateibasierten `LogStream`s. ## Fehler‑Logging Wenn ein Server keine Ausnahmen an den Debugger weitergibt, leitet er die Fehler zum Log‑Schreiben weiter, sofern welche vorhanden sind. Sie können das Fehler‑Logging folgendermaßen konfigurieren: ```cs config.ThrowExceptions = false; config.ErrorsLogsStream = new LogStream("error.log"); ``` Diese Einstellung schreibt nur dann etwas ins Log, wenn der Fehler nicht durch den Callback oder die Eigenschaft [Router.CallbackErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.CallbackErrorHandler.md) abgefangen wurde. Der vom Server geschriebene Fehler enthält stets Datum und Uhrzeit, die Request‑Header (nicht den Body), den Fehler‑Stacktrace und, falls vorhanden, den Stacktrace der inneren Ausnahme. ## Weitere Logging‑Instanzen Ihre Anwendung kann null oder mehrere `LogStream`s besitzen; es gibt keine Begrenzung, wie viele Log‑Kanäle sie haben darf. 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); ``` ## Dateien sicher schreiben Für Dateien, die keine Log‑Streams sind, wie Berichte oder Zustands‑Dumps, stellt [LogStream](https://docs.sisk-framework.org/api/Sisk.Core.Http.LogStream.md) statische Hilfsmethoden bereit, die das fehlende übergeordnete Verzeichnis anlegen, gleichzeitige Schreibvorgänge auf dieselbe Datei innerhalb des Prozesses serialisieren und `false` zurückgeben, anstatt eine Ausnahme zu werfen, wenn die Operation fehlschlägt: - [LogStream.SafeWriteToFile](https://docs.sisk-framework.org/api/Sisk.Core.Http.LogStream.SafeWriteToFile.md) und [LogStream.SafeWriteToFileAsync](https://docs.sisk-framework.org/api/Sisk.Core.Http.LogStream.SafeWriteToFileAsync.md) ersetzen den Dateiinhalt. - [LogStream.SafeAppendToFile](https://docs.sisk-framework.org/api/Sisk.Core.Http.LogStream.SafeAppendToFile.md) und [LogStream.SafeAppendToFileAsync](https://docs.sisk-framework.org/api/Sisk.Core.Http.LogStream.SafeAppendToFileAsync.md) hängen an die Datei an. Jede Methode akzeptiert einen String oder ein Array von Zeilen und hat Überladungen, die ein `Encoding` entgegennehmen. ```cs bool written = await LogStream.SafeAppendToFileAsync("logs/audit.log", $"{DateTime.Now:s} user signed in\n"); if (!written) { Console.WriteLine("Could not write the audit entry."); } ``` Die Sperre gilt pro Prozess: Schreibvorgänge anderer Prozesse auf dieselbe Datei werden nicht koordiniert. Wenn Sie lediglich sicherstellen wollen, dass ein Verzeichnis existiert, verwenden Sie [PathHelper.EnsureDirectoryExistance](https://docs.sisk-framework.org/api/Sisk.Core.Helpers.PathHelper.EnsureDirectoryExistance.md) oder [PathHelper.EnsureFileDirectoryExistance](https://docs.sisk-framework.org/api/Sisk.Core.Helpers.PathHelper.EnsureFileDirectoryExistance.md), die `true` zurückgeben, wenn das Verzeichnis bereits existierte, und `false`, wenn es neu erstellt wurde. ## Erweiterung von LogStream Sie können die Klasse `LogStream` erweitern, um eigene Formate zu schreiben, die mit der aktuellen Sisk‑Log‑Engine kompatibel sind. Das nachfolgende Beispiel ermöglicht das Schreiben farbiger Meldungen in die Konsole über die Bibliothek Spectre.Console: ```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 folgende Beispiel ist etwas umfangreicher. Es schreibt den Body von Anfrage und Antwort als JSON in die Konsole. Es kann für das allgemeine 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) { // An diesem Punkt 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 im Request gespeichert. // // Wird der Inhalt nicht in der Request‑Aktion gelesen, kann die Garbage‑Collection // den Inhalt nach dem Senden der Antwort an den Client freigeben, 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 besitzt 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 mit der Bibliothek CypherPotato.LightJson 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 { // Interner Server‑Verarbeitungsstatus ermitteln 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 Empfangen und Senden von Nachrichten an den Client. Diese Funktion funktioniert in den meisten Browsern einwandfrei, ist jedoch in Sisk noch experimentell. Bitte melden Sie etwaige Fehler auf GitHub. ## Nachrichten empfangen WebSocket-Nachrichten werden in Reihenfolge empfangen und warten in einer Warteschlange, bis sie von `ReceiveMessageAsync` verarbeitet werden. Diese Methode liefert keine Nachricht, wenn das Zeitlimit erreicht ist, die Operation abgebrochen wird oder der Client die Verbindung trennt. Die maximale Größe einer empfangenen Nachricht ist [HttpServerConfiguration.MaximumContentLength](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.MaximumContentLength.md) und beträgt seit Sisk 1.7 standardmäßig 100 MB. Sendet ein Client eine größere Nachricht, wird die Verbindung mit dem Status `MessageTooBig` geschlossen und `ReceiveMessageAsync` liefert keine Nachricht. Nur ein Lese‑ und Schreibvorgang kann gleichzeitig stattfinden; daher ist es während des Wartens auf eine Nachricht mit `ReceiveMessageAsync` nicht möglich, 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, um Nachrichten zu empfangen, zu verarbeiten und die Verbindung anschließend zu 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‑Side‑Events können Sie auch hier 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, sodass 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 serverseitige 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. --- # Aktivieren von CORS (Cross-Origin Resource Sharing) in Sisk Source: https://docs.sisk-framework.org/de/docs/features/cors.html Sisk verfügt über ein Werkzeug, das beim Umgang mit [Cross-Origin Resource Sharing (CORS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS) nützlich sein kann, wenn Sie Ihren Dienst öffentlich bereitstellen. Diese Funktion ist kein Teil des HTTP‑Protokolls, sondern ein spezifisches Merkmal von Webbrowsern, definiert vom W3C. Dieser Sicherheitsmechanismus verhindert, dass eine Webseite Anfragen an eine andere Domain stellt als die, von der die Seite bereitgestellt wurde. Ein Dienstanbieter kann bestimmten Domains den Zugriff auf seine Ressourcen erlauben, oder nur einer. ## Gleiche Herkunft Um eine Ressource als „gleiche Herkunft“ zu identifizieren, muss eine Anfrage den [Origin](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Origin)-Header in ihrer Anfrage angeben: ```http GET /api/users HTTP/1.1 Host: example.com Origin: http://example.com ... ``` Und der entfernte Server muss mit einem [Access-Control-Allow-Origin](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin)-Header antworten, der denselben Wert wie die angeforderte Herkunft enthält: ```http HTTP/1.1 200 OK Access-Control-Allow-Origin: http://example.com ... ``` Diese Überprüfung ist **explizit**: Host, Port und Protokoll müssen mit der Anfrage übereinstimmen. Betrachten Sie das Beispiel: - Ein Server gibt zurück, dass sein `Access-Control-Allow-Origin` `https://example.com` ist: - `https://example.net` – die Domain 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 ebenfalls weggelassen, wenn er ein Standardport ist (80 für HTTP und 443 für HTTPS). ```http Origin: null Origin: :// Origin: ://: ``` ## CORS aktivieren Standardmäßig steht Ihnen das Objekt [CrossOriginResourceSharingHeaders](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.md) innerhalb Ihres [ListeningHost](https://docs.sisk-framework.org/api/Sisk.Core.Http.ListeningHost.md) zur Verfügung. 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 Fehlermeldungen und Weiterleitungen. Sie werden feststellen, dass die Klasse [CrossOriginResourceSharingHeaders](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.md) zwei ähnliche Eigenschaften besitzt: [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 die eine im Plural, die andere im Singular steht. - Die **AllowOrigin**‑Eigenschaft ist statisch: Nur die von Ihnen angegebene Herkunft wird für alle Antworten gesendet. - Die **AllowOrigins**‑Eigenschaft ist dynamisch: Der Server prüft, ob die Herkunft der Anfrage in dieser Liste enthalten ist. Wird sie gefunden, wird sie für die Antwort dieser Herkunft gesendet. Wenn die erlaubte Herkunft von der Anfrage abhängt, also wenn Sie **AllowOrigins** oder den unten beschriebenen [AutoAllowOrigin](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AutoAllowOrigin.md)-Wert verwenden, sendet Sisk zusätzlich den Header `Vary: Origin`, damit Caches keine Antwort für eine andere Herkunft wiederverwenden. ### Platzhalter und automatische Header Alternativ können Sie ein Platzhalterzeichen (`*`) in der Herkunft der Antwort verwenden, um anzugeben, dass jede Herkunft Zugriff auf die Ressource hat. Dieser Wert ist jedoch für Anfragen mit Anmeldeinformationen (Autorisation‑Header) nicht zulässig und diese Operation [führt zu einem Fehler](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS/Errors/CORSNotSupportingCredentials). Sie können dieses Problem umgehen, indem Sie explizit auflisten, welche Herkünfte über die [AllowOrigins](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AllowOrigins.md)‑Eigenschaft erlaubt werden, oder indem Sie die Konstante [AutoAllowOrigin](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AutoAllowOrigin.md) im Wert von [AllowOrigin](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AllowOrigin.md) verwenden. Diese magische Eigenschaft definiert den `Access-Control-Allow-Origin`‑Header mit demselben Wert wie der `Origin`‑Header der Anfrage. Sie können außerdem [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 Verhalten ähnlich wie `AllowOrigin` verwenden, das automatisch basierend auf den gesendeten Headern antwortet. ```csharp using var host = HttpServer.CreateBuilder() .UseCors(new CrossOriginResourceSharingHeaders( // Antwortet basierend auf dem Origin-Header der Anfrage allowOrigin: CrossOriginResourceSharingHeaders.AutoAllowOrigin, // Antwortet basierend auf dem Access-Control-Request-Method-Header oder der Anfragemethode allowMethods: [CrossOriginResourceSharingHeaders.AutoFromRequestMethod], // Antwortet 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(); ``` ## Weitere Möglichkeiten, CORS anzuwenden Wenn Sie mit [service providers](https://docs.sisk-framework.org/de/docs/extensions/service-providers.md) arbeiten, können Sie Werte, die in der Konfigurationsdatei definiert sind, überschreiben: ```csharp static async Task Main(string[] args) { using var app = HttpServer.CreateBuilder() .UsePortableConfiguration(...) .UseCors(cors => { // Überschreibt den in der Konfiguration definierten Ursprung // Datei. cors.AllowOrigin = "http://example.com"; }) .Build(); await app.StartAsync(); } ``` ## Deaktivieren von CORS für bestimmte Routen ```csharp [RoutePrefix("api/widgets")] public class WidgetController : Controller { // GET /api/widgets/colors [RouteGet("/colors", UseCors = false)] public IEnumerable GetWidgets() { return new[] { "Green widget", "Red widget" }; } } ``` ## Werte in der Antwort ersetzen Sie können Werte in einer Router‑Aktion explizit 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-Header request.Context.OverrideHeaders.AccessControlAllowOrigin = "https://contorso.com"; return new[] { "Green widget", "Red widget" }; } } ``` ## Preflight-Anfragen Eine Preflight‑Anfrage ist eine [OPTIONS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Methods/OPTIONS)-Methoden‑Anfrage, die der Client vor der eigentlichen Anfrage sendet. Wenn eine Anfrage mit der Methode `OPTIONS` dem Pfad einer Route entspricht, aber keine Route explizit für `OPTIONS` konfiguriert ist, antwortet der Sisk‑Server mit einem `200 OK` und den zutreffenden CORS‑Headern, woraufhin der Client mit der eigentlichen Anfrage fortfahren kann. - Wenn eine Route explizit mit [RouteMethod.Options](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouteMethod.md) gemappt ist, zum Beispiel mit `MapOptions`, verarbeitet diese Route die Anfrage anstelle der automatischen Antwort. - Wenn die Route, die dem Pfad entspricht, `UseCors = false` hat, wird die automatische Antwort weiterhin gesendet, jedoch ohne CORS‑Header. > [!NOTE] > Vor Sisk 1.7 enthielt die automatische Preflight‑Antwort stets die CORS‑Header, selbst für Routen mit `UseCors = false`. ## CORS global deaktivieren Dies ist nicht möglich. Um CORS nicht zu verwenden, konfigurieren Sie es einfach 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 Range‑Anfragen (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; // mappt das Stammverzeichnis des Servers auf das aktuelle Verzeichnis mainRouter.MapFileSystem("/", Directory.GetCurrentDirectory()); // mappt /assets auf den Ordner "public/assets" mainRouter.MapFileSystem( "/assets", Path.Combine(Directory.GetCurrentDirectory(), "public", "assets")); ``` Wenn eine Anforderung dem Routen‑Präfix entspricht, sucht der `HttpFileServerHandler` im angegebenen Verzeichnis nach einer Datei. Wird sie gefunden, wird sie bereitgestellt; andernfalls wird eine 404‑Antwort (oder 403 bei verweigertem Zugriff) zurückgegeben. `HttpFileServer.CreateServingRoute` ist weiterhin verfügbar, wenn Sie ein `Route`‑Objekt explizit erstellen müssen, aber `MapFileSystem` ist die direkteste Option für Anwendungscode. Beide Methoden erzeugen eine [PrefixRoute](https://docs.sisk-framework.org/api/Sisk.Core.Routing.PrefixRoute.md) (eine Regex‑Route vor Sisk 1.7), die eine [dynamische Route](https://docs.sisk-framework.org/de/docs/fundamentals/routing.md#matching-precedence) ist. Statische Routen werden immer vorher getestet, sodass ein Dateiserver, der auf `/` gemappt ist, als Fallback für Pfade dient, die Ihre anderen Routen nicht verarbeiten. Dateiserver‑Routen werden nicht von [HttpServerConfiguration.ForceTrailingSlash](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ForceTrailingSlash.md) beeinflusst. ## 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"); // Verzeichnisauflistung aktivieren (standardmäßig deaktiviert) fileHandler.AllowDirectoryListing = true; // benutzerdefinierten Routenpräfix festlegen (dies wird vom Anforderungspfad entfernt) fileHandler.RoutePrefix = "/public"; // Registriert den Handler unter /public mainRouter.MapFileSystem("/public", fileHandler); ``` ### Konfiguration | Property | Description | |---|---| | `RootDirectoryPath` | Der absolute oder relative Pfad zum Stammverzeichnis, aus dem Dateien bereitgestellt werden. | | `RoutePrefix` | Der Routenpräfix, der 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, bestimmte Dateitypen abzufangen und anders zu verarbeiten. Beispielsweise möchten 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`: Verarbeitet `.mp3`, `.ogg`, `.wav`, `.flac`, `.ogv`. - `HttpFileVideoConverter`: Verarbeitet `.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()) }; } } ``` Fügen Sie es dann Ihrem Handler hinzu: ```cs var handler = new HttpFileServerHandler("./files"); handler.FileConverters.Add(new MyTextConverter()); ``` --- # Modell-Kontext-Protokoll Source: https://docs.sisk-framework.org/de/docs/extensions/mcp.html Es ist möglich, Anwendungen zu erstellen, die Agentenmodellen Kontext mithilfe großer Sprachmodelle (LLMs) bereitstellen, und zwar mit dem Paket [Sisk.ModelContextProtocol](https://www.nuget.org/packages/Sisk.ModelContextProtocol/) : dotnet add package Sisk.ModelContextProtocol --prerelease Dieses Paket stellt nützliche Klassen und Methoden zum Aufbau von MCP‑Servern bereit, die über [Streamable HTTP](https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http) arbeiten und einzelne Anfrage‑/Antwort‑Nachrichten unterstützen. Seit Version 1.7.0‑rc1 unterstützt es zustandslose Anfragen der Protokollversion `2026-07-28` und behält den Legacy‑Initialisierungsablauf für Clients bei, die die Versionen `2025-11-25`, `2025-06-18` und `2025-03-26` verwenden. Es unterstützt Werkzeuge und Skills. > [!NOTE] > > Bevor Sie beginnen, beachten Sie bitte, dass dieses Paket sich noch in der Entwicklung befindet und Verhaltensweisen zeigen 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 zur Definition 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 eine [HttpResponse](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpResponse.md) zurück, und Ihre Route muss diese zurückgeben. Wenn Sie mehrere Provider in einer Anwendung benötigen, überspringen Sie 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"] } ``` ## Umgang mit 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 das 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 als nicht erforderlich definiert, daher kann es hier null sein string? actionData = context.Arguments["action_data"].MaybeNull()?.GetString(); // Verarbeite die Browseraktion basierend auf dem actionName return await Task.FromResult( McpToolResult.CreateText($"Performed browser action: {actionName}")); })); ``` Tool‑Argumente werden vor dem Aufruf Ihres Handlers anhand des Schemas validiert. Bei einer fehlgeschlagenen Validierung gibt der Provider ein Fehl­ergebnis an den MCP‑Client zurück und ruft den Tool‑Handler nicht auf. ## Funktionsresultate Das Objekt [McpToolResult](https://docs.sisk-framework.org/api/Sisk.ModelContextProtocol.McpToolResult.md) stellt drei Methoden zum Erzeugen von Inhalten für eine Tool‑Antwort bereit: - [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‑Tool‑Antwort zu kombinieren: ```csharp mcp.Tools.Add(new McpTool( ... executionHandler: async (McpToolContext context) => { // simulate real work byte[] browserScreenshot = await browser.ScreenshotAsync(); return McpToolResult.Combine( McpToolResult.CreateText("Heres the screenshot of the browser:"), McpToolResult.CreateImage(browserScreenshot, "image/png") ) })); ``` Der Provider unterstützt derzeit `server/discover`, die Legacy‑Methoden `initialize` und `ping`, `tools/list`, `tools/call`, `skills/list`, `skills/get`, `resources/list`, `resources/read` sowie Benachrichtigungen. Nicht unterstützte JSON‑RPC‑Methoden liefern eine JSON‑RPC‑Fehlerantwort. ## Veröffentlichen von Skills Ein MCP‑Provider kann außerdem [Agent Skills](https://github.com/modelcontextprotocol/ext-skills) über die Erweiterung `io.modelcontextprotocol/skills` veröffentlichen. Fügen Sie [McpSkill](https://docs.sisk-framework.org/api/Sisk.ModelContextProtocol.McpSkill.md)-Objekte zu [McpProvider.Skills](https://docs.sisk-framework.org/api/Sisk.ModelContextProtocol.McpProvider.Skills.md) hinzu, bevor der Provider beginnt, Anfragen zu bearbeiten: ```csharp mcp.Skills.Add(new McpSkill( path: "acme/refunds", frontmatter: new JsonObject { ["name"] = "refunds", ["description"] = "Follow the refund workflow." }, body: "# Refunds\nRead references/policy.md before using the refund tools.", files: new Dictionary { ["references/policy.md"] = File.ReadAllBytes("skills/refunds/references/policy.md") })); ``` `McpSkill` erzeugt die Datei `SKILL.md` aus dem Frontmatter und dem Markdown‑Body. Das letzte Segment von `path` muss dem Feld `name` im Frontmatter entsprechen. Die Skill‑Dateien werden beim Erstellen des Skills kopiert und vom Provider nie wieder von der Festplatte gelesen. Clients listen und lesen Skills mit `skills/list`, `skills/get` und `resources/read` unter Verwendung der Protokollversion `2026-07-28`. Siehe die [Paketdetails](https://github.com/sisk-http/core/tree/main/extensions/Sisk.ModelContextProtocol) für Limits und Validierungsregeln. ## Weiterführende Arbeit Das Model Context Protocol 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/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 Anwendungsbereiche 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 (); ``` --- # Dienstanbieter Source: https://docs.sisk-framework.org/de/docs/extensions/service-providers.html Service Providers ist eine Möglichkeit, Ihre Sisk-Anwendung in verschiedene Umgebungen zu portieren, indem Sie eine portable Konfigurationsdatei verwenden. Diese Funktion ermöglicht es Ihnen, den Port des Servers, Parameter und andere Optionen zu ändern, ohne den Anwendungscode für jede Umgebung anpassen zu müssen. Dieses Modul hängt von der Sisk‑Bausyntax ab und kann über die UsePortableConfiguration‑Methode konfiguriert werden. Ein Konfigurationsanbieter wird mit `IConfigurationProvider` implementiert, das einen Konfigurationsleser bereitstellt und jede Implementierung erhalten kann. Standardmäßig liefert Sisk einen JSON‑Konfigurationsleser, es gibt jedoch auch ein Paket für INI‑Dateien. Sie können auch Ihren eigenen Konfigurationsanbieter erstellen und ihn registrieren 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 der Datei `service-config.json` gesucht, und zwar im aktuellen Verzeichnis des laufenden Prozesses, nicht im Verzeichnis der ausführbaren Datei. Sie können den Dateinamen sowie den Ort, an dem Sisk nach der Konfigurationsdatei suchen soll, ändern 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 die Datei `config.toml` im aktuellen Verzeichnis des laufenden Prozesses. Wird sie nicht gefunden, wird anschließend im Verzeichnis der ausführbaren Datei gesucht. Existiert die Datei nicht, wird der Parameter `createIfDontExists` berücksichtigt, die Datei (ohne Inhalt) im zuletzt getesteten Pfad (basierend auf `lookupDirectories`) erstellt, und es wird ein Fehler in der Konsole ausgegeben, der die Initialisierung der Anwendung verhindert. > [!TIP] > > Sie können sich den Quellcode des INI‑Konfigurationslesers und des JSON‑Konfigurationslesers ansehen, um zu verstehen, wie ein `IConfigurationProvider` implementiert ist. ## Lesen von Konfigurationen aus einer JSON‑Datei Standardmäßig stellt Sisk einen Konfigurationsanbieter bereit, 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": "My sisk application", "Ports": [ "http://localhost:80/", "https://localhost:443/", // Konfigurationsdateien unterstützen ebenfalls Kommentare ], "CrossOriginResourceSharingPolicy": { "AllowOrigin": "*", "AllowOrigins": [ "*" ], // new on 0.14 "AllowMethods": [ "*" ], "AllowHeaders": [ "*" ], "MaxAge": 3600 }, "Parameters": { "MySqlConnection": "server=localhost;user=root;" } } } ``` Die aus einer Konfigurationsdatei erzeugten Parameter können im Server‑Konstruktor 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 Server‑Initialisierungsparameter zu lesen. Einige Eigenschaften sollen in der Prozessumgebung angegeben werden, anstatt in der Konfigurationsdatei definiert zu sein, z. B. sensible API‑Daten, API‑Schlüssel usw. ## Struktur der Konfigurationsdatei Die JSON‑Konfigurationsdatei besteht aus den folgenden Eigenschaften:
    Eigenschaft Erforderlich Beschreibung
    Server Required Stellt den Server selbst mit seinen Einstellungen dar.
    Server.AccessLogsStream Optional Standardmäßig console. Gibt den Ausgabestream für das Zugriffsprotokoll an. Kann ein Dateiname, null oder console sein.
    Server.ErrorsLogsStream Optional Standardmäßig null. Gibt den Ausgabestream für das Fehlerprotokoll an. Kann ein Dateiname, null oder console sein.
    Server.MaximumContentLength Optional
    Server.MaximumContentLength Optional Gibt die maximale Inhaltslänge in Bytes an. Null bedeutet unbegrenzt. Wenn das Server-Objekt vorhanden ist und diese Eigenschaft weggelassen wird, ist der Wert 0 (unbegrenzt), was den Standardwert von 100 MB von HttpServerConfiguration.MaximumContentLength überschreibt.
    Server.IncludeRequestIdHeader Optional Standardmäßig false. Gibt an, ob der HTTP‑Server den Header X-Request-Id senden soll.
    Server.ThrowExceptions Optional Standardmäßig true. Gibt an, ob nicht behandelte Ausnahmen geworfen werden sollen. In der Produktion auf false setzen und beim Debuggen auf true.
    ListeningHost Required Stellt den Host dar, an dem der Server lauscht.
    ListeningHost.Label Optional Stellt die Anwendungsbezeichnung dar.
    ListeningHost.Ports Required Stellt ein Array von Zeichenketten dar, das der Syntax von ListeningPort entspricht.
    ListeningHost.CrossOriginResourceSharingPolicy Optional Richtet die CORS‑Header für die Anwendung ein.
    ListeningHost.CrossOriginResourceSharingPolicy.AllowCredentials Optional Standardmäßig false. Gibt den Header Allow-Credentials an.
    ListeningHost.CrossOriginResourceSharingPolicy.ExposeHeaders Optional Standardmäßig null. Diese Eigenschaft erwartet ein Array von Zeichenketten. Gibt den Header Expose-Headers an.
    ListeningHost.CrossOriginResourceSharingPolicy.AllowOrigin Optional Standardmäßig null. Diese Eigenschaft erwartet eine Zeichenkette. Gibt den Header Allow-Origin an.
    ListeningHost.CrossOriginResourceSharingPolicy.AllowOrigins Optional Standardmäßig null. Diese Eigenschaft erwartet ein Array von Zeichenketten. Gibt mehrere Allow-Origin-Header an. Siehe AllowOrigins für weitere Informationen.
    ListeningHost.CrossOriginResourceSharingPolicy.AllowMethods Optional Standardmäßig null. Diese Eigenschaft erwartet ein Array von Zeichenketten. Gibt den Header Allow-Methods an.
    ListeningHost.CrossOriginResourceSharingPolicy.AllowHeaders Optional Standardmäßig null. Diese Eigenschaft erwartet ein Array von Zeichenketten. Gibt den Header Allow-Headers an.
    ListeningHost.CrossOriginResourceSharingPolicy.MaxAge Optional Standardmäßig null. Diese Eigenschaft erwartet einen Integer. Gibt den Header Max-Age in Sekunden an.
    ListeningHost.Parameters Optional Gibt die Eigenschaften an, die der Setup‑Methode der Anwendung übergeben werden.
    --- # INI-Konfigurationsanbieter Source: https://docs.sisk-framework.org/de/docs/extensions/ini-configuration.html Sisk bietet eine Methode zum Laden von Startkonfigurationen, 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 beliebigen Dateitypen zu lesen. Das Paket [Sisk.IniConfiguration](https://www.nuget.org/packages/Sisk.IniConfiguration/) stellt einen streambasierten INI-Dateileser bereit, der bei üblichen Syntaxfehlern keine Ausnahmen wirft und eine einfache Konfigurationssyntax verwendet. Dieses Paket kann außerhalb des Sisk‑Frameworks eingesetzt werden und bietet Flexibilität für Projekte, die einen effizienten INI‑Dokumentenleser benötigen. ## Installation Um das Paket zu installieren, können Sie beginnen mit: ```bash $ dotnet add package Sisk.IniConfiguration ``` Sie können auch das Kernpaket installieren, das den INI [IConfigurationReader](https://docs.sisk-framework.org/api/Sisk.Core.Http.Hosting.IConfigurationReader) nicht enthält, weder die Sisk‑Abhängigkeit, 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($"Hello, {name}!"); } } ``` Der obige Code sucht nach einer app.ini‑Datei im aktuellen Arbeitsverzeichnis des Prozesses (CurrentDirectory). Die INI‑Datei sieht folgendermaßen aus: ```ini [Server] # Mehrere Listenadressen 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-Variante und Syntax Aktuelle Implementierungsvariante: - Eigenschaften und Abschnittsnamen sind **nicht case‑sensitiv**. - Eigenschaftsnamen und -werte werden **getrimmt**, es sei denn, die Werte sind in Anführungszeichen. - Werte können mit einfachen oder doppelten Anführungszeichen umschlossen werden. Anführungszeichen können Zeilenumbrüche enthalten. - Kommentare werden mit `#` und `;` unterstützt. Außerdem sind **nachgestellte Kommentare erlaubt**. - Eigenschaften können mehrere Werte besitzen. Im Detail ist die Dokumentation zur „Variante“ des in Sisk verwendeten INI‑Parsers [in diesem Dokument]https://github.com/sisk-http/archive/blob/master/ext/ini-reader-syntax.md verfügbar. Verwenden Sie den folgenden INI-Code als Beispiel: ```ini One = 1 Value = this is an value Another value = "this value has an line break on it" ; der nachfolgende Code hat einige Farben [some section] Color = Red Color = Blue Color = Yellow ; verwende kein Gelb ``` Parse it with: ```csharp // parse den INI-Text aus dem String IniDocument doc = IniDocument.FromString(iniText); // einen Wert holen string? one = doc.Global.GetOne("one"); string? anotherValue = doc.Global.GetOne("another value"); // mehrere Werte holen string[]? colors = doc.GetSection("some section")?.GetMany("color"); ``` ## Konfigurationsparameter | Abschnitt und Name | Mehrere Werte zulassen | Beschreibung | | ------------------ | ---------------------- | ------------ | | `Server.Listen` | Yes | Die vom Server zu hörenden Adressen/Ports. | | `Server.Encoding` | No | Die Standardkodierung des Servers. | | `Server.MaximumContentLength` | No | Die maximale Content‑Length‑Größe des Servers in Bytes. Wenn weggelassen, bleibt die Server‑Standardgröße erhalten (100 MB seit Sisk 1.7). Verwenden Sie `0` für kein Limit. | | `Server.IncludeRequestIdHeader` | No | Gibt an, ob der HTTP‑Server den X-Request-Id‑Header senden soll. | | `Server.ThrowExceptions` | No | Gibt an, ob unbehandelte Ausnahmen geworfen werden sollen. | | `Server.AccessLogsStream` | No | Gibt den Ausgabestream für Zugriffsprotokolle an. | | `Server.ErrorsLogsStream` | No | Gibt den Ausgabestream für Fehlermeldungen an. | | `Cors.AllowMethods` | No | Gibt den Wert des CORS Allow-Methods‑Headers an. | | `Cors.AllowHeaders` | No | Gibt den Wert des CORS Allow-Headers‑Headers an. | | `Cors.AllowOrigins` | No | Gibt mehrere Allow-Origin‑Header an, getrennt durch Kommas. [AllowOrigins](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AllowOrigins.md) für weitere Informationen. | | `Cors.AllowOrigin` | No | Gibt einen einzelnen Allow-Origin‑Header an. | | `Cors.ExposeHeaders` | No | Gibt den Wert des CORS Expose-Headers‑Headers an. | | `Cors.AllowCredentials` | No | Gibt den Wert des CORS Allow-Credentials‑Headers an. | | `Cors.MaxAge` | No | 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 und unterstützt den Export in das Open‑API‑Format (Swagger). > [!WARNING] > Dieses Paket wird als Vorabversion (1.7.0‑rc3) veröffentlicht. Sein Verhalten und die API können sich in zukünftigen Updates ändern. Installieren Sie das [Sisk.Documenting](https://www.nuget.org/packages/Sisk.Documenting/)‑Paket von NuGet. Um die Dokumentation als HTML‑Seite bereitzustellen, installieren Sie zusätzlich [Sisk.Documenting.Html](https://www.nuget.org/packages/Sisk.Documenting.Html/): ```sh dotnet add package Sisk.Documenting --prerelease dotnet add package Sisk.Documenting.Html --prerelease ``` Der Quellcode ist [hier](https://github.com/sisk-http/core/tree/main/extensions/Sisk.Documenting) verfügbar. Um `Sisk.Documenting` zu verwenden, müssen Sie es im Application‑Builder registrieren und Ihre Route‑Handler mit Dokumentations‑Attributen versehen. ### Registrierung der Dokumentations‑Generierung Verwenden Sie die Erweiterungsmethode `UseApiDocumentation` auf Ihrem `HttpServerHostContextBuilder`, um die erzeugte 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` aktiviert die Open‑API‑(Swagger‑)Unterstützung. ### Dokumentation von Endpunkten Sie können Ihre Endpunkte mit den Attributen `[ApiEndpoint]` und `[ApiQueryParameter]` auf Ihren Route‑Handler‑Methoden beschreiben. ### `ApiEndpoint` Das `[ApiEndpoint]`‑Attribut ermöglicht 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, im Konstruktor erforderlich): 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, Standard: `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, im Konstruktor erforderlich): 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, im Konstruktor erforderlich): Der Name des Parameters. * **TypeName** (string, im Konstruktor erforderlich): Der Datentyp des Parameters (z. B. „string“, „int“). * **Description** (string): Eine Beschreibung des Parameters. * **IsRequired** (bool): Gibt an, ob der Parameter zwingend erforderlich ist. ### `ApiParameterExample` Fügt einem dokumentierten Parameter in einer bestimmten Situation einen Beispielwert hinzu. Das Attribut kann mehrfach auf dieselbe Methode angewendet werden. * **ParameterName** (string, im Konstruktor erforderlich): Der Name des Parameters, für den das Beispiel gilt. * **Title** (string, im Konstruktor erforderlich): Ein Titel, der die durch das Beispiel dargestellte Situation identifiziert. * **Description** (string): Wann oder warum das Beispiel gilt. * **Example** (string): Der Beispielwert. * **ExampleLanguage** (string): Die Sprache, in der der Beispielwert formatiert ist, falls zutreffend. ```csharp [ApiQueryParameter("status", Type = "string", Description = "Filters orders by status.")] [ApiParameterExample("status", "Pending orders", Example = "pending")] [ApiParameterExample("status", "Shipped orders", Example = "shipped", Description = "Orders that left the warehouse.")] ``` ### `ApiParametersFrom` Erzeugt automatisch Parameter‑Dokumentation aus den Eigenschaften einer angegebenen Klasse oder eines Typs. * **Type** (Type, im Konstruktor erforderlich): Der Klassen‑`Type`, aus dem die Eigenschaften reflektiert werden sollen. ### `ApiPathParameter` Dokumentiert eine Pfad‑Variable (z. B. in `/users/{id}`). * **Name** (string, im Konstruktor erforderlich): 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, im Konstruktor erforderlich): Der Schlüssel des Abfrage‑Parameters. * **Description** (string): Beschreibt den Parameter. * **Type** (string): Der erwartete Datentyp. * **IsRequired** (bool): Gibt an, ob der Abfrage‑Parameter vorhanden sein muss. ### `ApiRequest` Beschreibt den erwarteten Request‑Body. * **Description** (string, im Konstruktor erforderlich): Eine Beschreibung des Request‑Bodies. * **Example** (string): Ein roher 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, im Konstruktor erforderlich): Der zurückgegebene HTTP‑Statuscode (z. B. `HttpStatusCode.OK`). * **Description** (string): Beschreibt die Bedingung für diese Antwort. * **Example** (string): Ein roher 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. ## Type Handlers 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 der Serialisierungs‑Logik Ihrer Anwendung zu entsprechen. ```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 die Erzeugung von Beispielen anzupassen. #### 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) { // XML-String für den Typ erzeugen 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" } ``` ### LlmsApiExporter Der `LlmsApiExporter` erzeugt ein Klartext‑Dokument im [llms.txt](https://llmstxt.org/)-Stil, das für Sprachmodelle gedacht ist. Sie können den in den Beispielen verwendeten `Hostname` sowie einen `HeaderText` und einen `FooterText` festlegen, die am Anfang bzw. Ende des Dokuments geschrieben werden: ```csharp new LlmsApiExporter( hostname: "https://api.example.com", headerText: "Public API of the Example service.", footerText: "Contact support@example.com for an API key.") ``` ### Creating a Custom Exporter Sie können Ihren eigenen Exporter erstellen, indem Sie das Interface `IApiDocumentationExporter` implementieren. Damit können Sie 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. Verarbeite das Dokumentationsobjekt 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. Gib den Inhalt als HttpContent zurück 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 erzeugte Dokumentation für die API „My application“ und beschreibt den `GET /`‑Endpunkt sowie dessen `name`‑Parameter. --- # Basic Auth (eingestellt) Source: https://docs.sisk-framework.org/de/docs/extensions/basic-auth.html > [!CAUTION] > **Eingestellt, nicht verwenden.** Das Sisk.BasicAuth‑Paket wird nicht mehr gepflegt und ist auf NuGet veraltet, ohne Ersatzpaket. Sein `BasicAuthenticateRequestHandler` dekodiert die Base64‑Anmeldeinformationen, die von Standard‑HTTP‑Clients gesendet werden, nicht, sodass authentifizierte Anfragen abgelehnt werden. Implementieren Sie die Basic‑Authentifizierung mit Ihrem eigenen [request handler](https://docs.sisk-framework.org/de/docs/fundamentals/request-handlers.md), das [HttpHeaderCollection.Authorization](https://docs.sisk-framework.org/api/Sisk.Core.Entity.HttpHeaderCollection.Authorization.md) ausliest. Diese Seite wird nur zu historischen Zwecken bereitgehalten. Das Basic‑Auth‑Paket fügt einen Request‑Handler hinzu, der das Basic‑Authentifizierungsschema in Ihrer Sisk‑Anwendung mit sehr wenig Konfiguration und Aufwand verarbeiten kann. Basic‑HTTP‑Authentifizierung ist eine minimale Eingabeform zur Authentifizierung von Anfragen mittels Benutzer‑ID und Passwort, wobei die Sitzung ausschließlich vom Client gesteuert wird und keine Authentifizierungs‑ oder Zugriffstoken vorhanden sind. ![Basic-Authentifizierung](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/Web/HTTP/Authentication). ## Installation Um zu beginnen, installieren Sie das Sisk.BasicAuth‑Paket in Ihrem Projekt: > dotnet add package Sisk.BasicAuth Sie können weitere Installationsmöglichkeiten für Ihr Projekt im [NuGet‑Repository](https://www.nuget.org/packages/Sisk.BasicAuth/0.15.0) einsehen. ## Erstellen Ihres Auth‑Handlers Sie können das Authentifizierungsschema für ein ganzes Modul oder für einzelne Routen steuern. Dafür schreiben wir zunächst unseren ersten Basic‑Authentifizierungs‑Handler. In dem nachfolgenden Beispiel wird eine Verbindung zur Datenbank hergestellt, geprüft, ob der Benutzer existiert und das Passwort gültig ist, und anschließend wird der Benutzer im Context‑Bag gespeichert. ```cs public class UserAuthHandler : BasicAuthenticateRequestHandler { public UserAuthHandler() : base() { Realm = "To enter this page, please, inform your credentials."; } public override HttpResponse? OnValidating(BasicAuthenticationCredentials credentials, HttpContext context) { DbContext db = new DbContext(); // In diesem Fall verwenden wir die E‑Mail als Feld für die Benutzer‑ID, // daher suchen wir nach einem Benutzer anhand seiner E‑Mail. User? user = db.Users.FirstOrDefault(u => u.Email == credentials.UserId); if (user == null) { return base.CreateUnauthorizedResponse("Sorry! No user was found by this email."); } // prüft, ob das Passwort der Anmeldeinformationen für diesen Benutzer gültig ist. if (!user.ValidatePassword(credentials.Password)) { return base.CreateUnauthorizedResponse("Invalid credentials."); } // fügt den angemeldeten Benutzer zum HttpContext hinzu // und setzt die Ausführung fort context.Bag.Add("loggedUser", user); return null; } } ``` So verbinden Sie diesen Request‑Handler einfach mit unserer Route oder Klasse. ```cs public class UsersController { [RouteGet("/")] [RequestHandler(typeof(UserAuthHandler))] public string Index(HttpRequest request) { User loggedUser = request.Bag.Get(); return $"Hello, {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() { // all routes inside this class will be handled by // UserAuthHandler. base.HasRequestHandler(new UserAuthHandler()); } [RouteGet("/")] public string Index(HttpRequest request) { User loggedUser = request.Bag.Get(); return $"Hello, {loggedUser.Name}!"; } } ``` ## Anmerkungen Die Hauptverantwortung der Basic‑Authentifizierung liegt auf der Client‑Seite. Speicherung, Cache‑Steuerung und Verschlüsselung werden allesamt lokal beim Client durchgeführt. Der Server erhält lediglich die Anmeldeinformationen und prüft, ob der Zugriff erlaubt ist oder nicht. Beachten Sie, dass diese Methode nicht zu den sichersten gehört, da sie eine erhebliche Verantwortung auf den Client legt, was die Nachverfolgung und Aufrechterhaltung der Sicherheit seiner Anmeldeinformationen erschweren kann. Außerdem ist es entscheidend, dass Passwörter in einem sicheren Verbindungs‑Kontext (SSL) übertragen werden, da sie keine eigene Verschlüsselung besitzen. Ein kurzer Abfangvorgang in den Headern einer Anfrage kann die Zugangsdaten Ihres Benutzers preisgeben. Setzen Sie für produktive Anwendungen auf robustere Authentifizierungslösungen und vermeiden Sie die Verwendung zu vieler vorgefertigter Komponenten, da diese möglicherweise nicht den Anforderungen Ihres Projekts entsprechen und es Sicherheitsrisiken aussetzen. --- # SSL-Proxy (eingestellt) Source: https://docs.sisk-framework.org/de/docs/extensions/ssl-proxy.html > [!CAUTION] > **Eingestellt, nicht verwenden.** Das Paket Sisk.SslProxy wird nicht mehr gepflegt und ist auf NuGet veraltet, ohne Ersatzpaket. Es lässt sich nicht mehr gegen die aktuelle Cadente‑API kompilieren. Um HTTPS bereitzustellen, verwenden Sie die native SSL‑Unterstützung der [Cadente engine](https://docs.sisk-framework.org/de/docs/cadente.md) oder eine der Optionen in [Working with SSL](https://docs.sisk-framework.org/de/docs/ssl.md). Diese Seite wird nur zu historischen Zwecken aufbewahrt. Der Sisk SSL Proxy ist ein Modul, das für einen [ListeningHost](https://docs.sisk-framework.org/api/Sisk.Core.Http.ListeningHost.md) in Sisk eine HTTPS‑Verbindung bereitstellt und HTTPS‑Nachrichten an einen unsicheren HTTP‑Kontext weiterleitet. Das Modul wurde entwickelt, um SSL‑Verbindungen für einen Dienst zu ermöglichen, der [HttpListener](https://learn.microsoft.com/pt-br/dotnet/api/system.net.httplistener?view=net-8.0) verwendet, welcher SSL nicht unterstützt. Der Proxy läuft innerhalb derselben Anwendung und lauscht auf HTTP/1.1‑Nachrichten, die er im selben Protokoll an Sisk weiterleitet. Derzeit ist diese Funktion stark experimentell und kann so instabil sein, dass sie nicht in der Produktion eingesetzt werden sollte. Derzeit unterstützt der SslProxy fast alle HTTP/1.1‑Funktionen, wie Keep‑Alive, Chunked‑Encoding, WebSockets usw. Für eine offene Verbindung zum SSL‑Proxy wird eine TCP‑Verbindung zum Zielserver hergestellt, 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("Hello, world!"); }); }) // 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. Damit das Zertifikat von Browsern akzeptiert wird, denken Sie daran, es in das Betriebssystem zu importieren, sodass es korrekt funktioniert. --- # 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 zusammenstellen 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 möchten: einen `Router`, ein oder mehrere `ListeningHost`‑Objekte, eine `HttpServerConfiguration` und den endgültigen `HttpServer`. Zuerst müssen wir das Request/Response‑Konzept verstehen. Es ist ganz einfach: Für jede Anfrage muss eine Antwort existieren. Sisk folgt diesem Prinzip ebenfalls. Lassen Sie uns eine Methode erstellen, die mit einer „Hello, World!“-Nachricht in HTML antwortet und dabei den Statuscode sowie die 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 besteht darin, diese Methode mit einer HTTP‑Route zu verknüpfen. ## Router Router sind Abstraktionen von Anforderungsrouten und dienen als Brücke zwischen Anfragen und Antworten des Dienstes. Router verwalten Service‑Routen, Funktionen und Fehler. Ein Router kann mehrere Routen besitzen, und jede Route kann unterschiedliche Operationen für diesen Pfad ausführen, z. B. eine Funktion ausführen, eine Seite bereitstellen oder eine Ressource vom Server liefern. Lassen Sie uns unseren ersten Router erstellen und unsere `IndexPage`‑Methode mit dem Index‑Pfad verknüpfen. ```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 einen Server gebunden, sodass er allein nicht funktioniert. Der nächste Schritt besteht darin, 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 verweist: ```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 seine 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 unser ListeningHost zu dieser Serverkonfiguration hinzu ``` Gemeinsame Serverkonfigurationsoptionen: | 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. | Setzen Sie `Drop` nur ein, 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 Antwort‑Header `X-Request-Id`. | Kombinieren Sie dies mit Logs, die `HttpRequest.RequestId` enthalten. | | [IdleConnectionTimeout](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.IdleConnectionTimeout.md) | `120` seconds | 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 Kodierungsinkonsistenz. | Dies verursacht Verarbeitungsaufwand; lassen Sie es deaktiviert, sofern nicht erforderlich. | | [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 sichtbar machen. | Deaktivieren Sie ihn für strengere Header‑Richtlinien in der Produktion. | | [OptionsLogMode](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.OptionsLogMode.md) | `LogOutput.Both` | Sie möchten die durch die 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 eine 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` | Request‑Bag‑Werte, die `IDisposable` implementieren, sollten automatisch freigegeben werden. | Aktiviert lassen, sofern die Besitzrechte nicht anderweitig verwaltet werden. | | [ConvertIAsyncEnumerableIntoEnumerable](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ConvertIAsyncEnumerableIntoEnumerable.md) | `true` | Wert‑Handler sollten asynchrone Enumerables als blockierende Enumerable‑Werte erhalten. | Deaktivieren Sie dies, wenn Sie Ihre 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 Sie dies für Clients oder Zwischensysteme, die persistente Verbindungen nicht gut handhaben. | | [ForceTrailingSlash](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ForceTrailingSlash.md) | `false` | GET‑Routen sollten zu einer URL mit abschließendem Schrägstrich umleiten. | Gilt nur für nicht‑regex‑basierte Routen. | | [MaximumContentLength](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.MaximumContentLength.md) | `100 MB` | Request‑Bodies oder WebSocket‑Nachrichten benötigen ein anderes Größenlimit. | Der Standard war vor Sisk 1.7 `0` (unbegrenzt). Setzen Sie ihn auf `0`, um das Limit zu entfernen. | | [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. | Vorhandene `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 unsere ausführbare Datei kompilieren und unseren HTTP‑Server mit dem Befehl starten: ```bash dotnet watch ``` Zur Laufzeit öffnen Sie Ihren Browser und navigieren zum Server‑Pfad; Sie sollten sehen: --- # Request-Lebenszyklus 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 eigentlichen Anfrage und der Antwort, die dem Client zugestellt wird. Dieser Kontext stammt vom integrierten Listener in Sisk, der entweder [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 Anfragenvalidierung: Die Validierung von [HttpServerConfiguration.RemoteRequestsAction](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.RemoteRequestsAction.md) wird für die Anfrage durchgeführt. - Ist die Anfrage extern und ist die Eigenschaft `Drop`, wird die Verbindung ohne Antwort an den Client geschlossen mit einem `HttpServerExecutionStatus = RemoteRequestDropped`. - Forwarding‑Resolver‑Konfiguration: Ist ein [ForwardingResolver](https://docs.sisk-framework.org/de/docs/advanced/forwarding-resolvers.md) konfiguriert, 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: Nachdem der Host aufgelöst wurde und mehr als ein [ListeningHost](https://docs.sisk-framework.org/api/Sisk.Core.Http.ListeningHost.md) konfiguriert ist, sucht der Server nach dem entsprechenden Host für die Anfrage. - Falls kein ListeningHost passt, wird dem Client eine 400 Bad Request‑Antwort zurückgegeben und dem HTTP‑Kontext der Status `HttpServerExecutionStatus = DnsUnknownHost` übermittelt. - Passt ein ListeningHost, dessen [Router](https://docs.sisk-framework.org/api/Sisk.Core.Http.ListeningHost.Router.md) jedoch noch nicht initialisiert ist, wird dem Client eine 503 Service Unavailable‑Antwort zurückgegeben und dem HTTP‑Kontext der Status `HttpServerExecutionStatus = ListeningHostNotReady` übermittelt. - Router‑Bindung: Der Router des entsprechenden ListeningHost wird dem empfangenen HTTP‑Server zugeordnet. - Ist der Router bereits einem anderen HTTP‑Server zugeordnet, was nicht zulässig 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 bei der Erstellung des HTTP‑Kontexts. - Vordefinition von Headern: - Definiert den Header `X-Request-Id` in der Antwort vor, falls dies konfiguriert ist. - Definiert den Header `X-Powered-By` in der Antwort vor, falls dies konfiguriert ist. - Validierung der Inhaltsgröße: Prüft, ob der Anfrageninhalt kleiner ist als [HttpServerConfiguration.MaximumContentLength](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.MaximumContentLength.md) (standardmäßig 100 MB), sofern er größer als null ist. - Sendet die Anfrage ein `Content-Length`, das größer ist als das konfigurierte, wird dem Client eine 413 Payload Too Large‑Antwort zurückgegeben und dem HTTP‑Kontext der Status `HttpServerExecutionStatus = ContentTooLarge` übermittelt. - 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. - Findet der Router keine Route, die zur Anfrage passt: - Ist die Eigenschaft [Router.NotFoundErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.NotFoundErrorHandler.md) konfiguriert, wird die Aktion aufgerufen und die Antwort der Aktion an den HTTP‑Client weitergeleitet. - Ist die vorherige Eigenschaft null, wird dem Client eine standardmäßige 404 Not Found‑Antwort zurückgegeben. - Findet der Router eine passende Route, aber die Methode der Route stimmt nicht mit der Methode der Anfrage überein: - Ist die Eigenschaft [Router.MethodNotAllowedErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.MethodNotAllowedErrorHandler.md) konfiguriert, wird die Aktion aufgerufen und die Antwort der Aktion an den HTTP‑Client weitergeleitet. - Ist die vorherige Eigenschaft null, wird dem Client eine standardmäßige 405 Method Not Allowed‑Antwort zurückgegeben. - Ist die Anfrage von der Methode `OPTIONS`: - Gibt der Router nur dann eine 200 Ok‑Antwort an den Client zurück, wenn keine Route mit der Anfragemethode übereinstimmt (die Methode der Route ist nicht explizit [RouteMethod.Options](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouteMethod.md)). - Ist die Eigenschaft [HttpServerConfiguration.ForceTrailingSlash](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ForceTrailingSlash.md) aktiviert, die gefundene Route kein Regex ist, der Anforderungspfad nicht mit `/` endet und die Anfragemethode `GET` ist: - Wird dem Client eine 307 Temporary Redirect‑HTTP‑Antwort mit dem `Location`‑Header zurückgegeben, der Pfad und Abfrage zur gleichen Adresse mit einem abschließenden `/` enthält. - 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 dem Client zurückgegeben. - Ist die vorherige Eigenschaft nicht definiert, wird eine leere Antwort an den Server zurückgegeben, der dann eine Antwort basierend auf dem Typ der ausgelösten Ausnahme weiterleitet, üblicherweise 500 Internal Server Error. - Alle [IRequestHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.IRequestHandler.md)-Instanzen, die in der Route definiert sind und das Flag `BeforeResponse` besitzen, 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 dem Client zurückgegeben. - Ist die vorherige Eigenschaft nicht definiert, wird eine leere Antwort an den Server zurückgegeben, der dann eine Antwort basierend auf dem Typ der ausgelösten Ausnahme weiterleitet, üblicherweise 500 Internal Server Error. - 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 dem Client zurückgegeben. - Ist die vorherige Eigenschaft nicht definiert, wird eine leere Antwort an den Server zurückgegeben, der dann eine Antwort basierend auf dem Typ der ausgelösten Ausnahme weiterleitet, üblicherweise 500 Internal Server Error. - 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 dem Client zurückgegeben. - Ist die vorherige Eigenschaft nicht definiert, wird eine leere Antwort an den Server zurückgegeben, der dann eine Antwort basierend auf dem Typ der ausgelösten Ausnahme weiterleitet, üblicherweise 500 Internal Server Error. - Alle [IRequestHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.IRequestHandler.md)-Instanzen, die in der Route definiert sind und das Flag `AfterResponse` besitzen, 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 dem Client zurückgegeben. - Ist die vorherige Eigenschaft nicht definiert, wird eine leere Antwort an den Server zurückgegeben, der dann eine Antwort basierend auf dem Typ der ausgelösten Ausnahme weiterleitet, üblicherweise 500 Internal Server Error. - **Verarbeiten der Antwort:** Sobald die Antwort bereit ist, bereitet der Server sie für das Senden an den Client vor. - Die Header der Cross‑Origin Resource Sharing‑Policy (CORS) werden in der Antwort gemäß der im aktuellen [ListeningHost.CrossOriginResourceSharingPolicy](https://docs.sisk-framework.org/api/Sisk.Core.Http.ListeningHost.CrossOriginResourceSharingPolicy.md) konfigurierten Richtlinie festgelegt. - 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 Antwortbytes 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. - Wurde auf dem Server eine Ausnahme ausgelöst, wird das Ereignis `OnException` für alle konfigurierten HTTP‑Server‑Handler aufgerufen. - Erlaubt die Route 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 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 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 z. B. die Behandlung von Http‑Anfragen, Routern, Kontext‑Bags und mehr. Die Klasse bündelt Ereignisse, die während der Lebensdauer des gesamten HTTP‑Servers und auch einer Anfrage auftreten. Das Http‑Protokoll verfügt nicht über 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 unterstützen. Bitte lesen Sie [diese Seite](https://docs.sisk-framework.org/api/Sisk.Core.Http.Handlers.HttpServerHandler.md), um zu erfahren, 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) ansehen, um zu verstehen, was bei einer Anfrage passiert und wo Ereignisse ausgelöst werden. Der HTTP‑Server ermöglicht 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 nur eine Instanz pro `HttpServerHandler` 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 seinem Kontext‑Bag 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 `GetDbContext`‑Erweiterung, einen Verbindungs‑Kontext direkt aus dem HttpRequest‑Objekt zu erstellen. Eine nicht freigegebene Verbindung kann beim Arbeiten 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‑Kontext 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 })); } } ``` ## Von Handlern ausgelöste Ausnahmen Seit Sisk 1.7 werden von Server‑Handlern ausgelöste Ausnahmen wie folgt behandelt: - Ausnahmen, die in `OnServerStarting`, `OnServerStarted` und `OnSetupRouter` ausgelöst werden, stoppen den Serverstart. Sie werden in eine `InvalidOperationException` gewrappt, wobei die ursprüngliche Ausnahme in `InnerException` enthalten ist. - Ausnahmen, die in anderen Ereignissen ausgelöst werden, werden in [ErrorsLogsStream](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ErrorsLogsStream.md) geschrieben und die übrigen Handler werden weiterhin aufgerufen, es sei denn, [ThrowExceptions](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ThrowExceptions.md) ist aktiviert. Ist `ThrowExceptions` aktiviert, werden sie in eine `InvalidOperationException` gewrappt. - Ausnahmen, die in `OnHttpRequestClose` und `OnException` oder beim Formatieren der Zugriffs‑ und Fehlerprotokolle ausgelöst werden, entkommen niemals der HTTP‑Engine, selbst wenn `ThrowExceptions` aktiviert ist. Sie werden in `ErrorsLogsStream` geschrieben. In früheren Versionen konnten sie den Prozess beenden oder die Verbindung offen lassen. Entwickler können mit dieser Klasse Sitzungen, Kontexte und Datenbankverbindungen implementieren. Der bereitgestellte Code zeigt ein praktisches Beispiel mit dem DatabaseConnectionHandler, das die Freigabe von Datenbankverbindungen am Ende jeder Anfrage automatisiert. Die Integration ist unkompliziert, da Handler während der Server‑Einrichtung registriert werden. Die Klasse HttpServerHandler bietet ein leistungsfähiges Werkzeugset zur Ressourcenverwaltung 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.