Sisk

Anfragen

Diese Seite wurde automatisch aus dem Englischen übersetzt. Original lesen

Anfragen sind Strukturen, die eine HTTP-Anforderungsnachricht darstellen. Das HttpRequest 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:

C#
static HttpResponse Index(HttpRequest request)
{
    HttpMethod requestMethod = request.Method;
    ...
}

Diese Eigenschaft gibt die Methode der Anfrage zurück, dargestellt durch ein HttpMethod Objekt.

Hinweis

Im Gegensatz zu Routemethoden dient diese Eigenschaft nicht dem RouteMethod.Any 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/[email protected]
KomponentennameBeschreibungKomponentenwert
PathGibt den Anforderungspfad zurück./user/login
FullPathGibt den Anforderungspfad und die Abfragezeichenfolge zurück./user/[email protected]
FullUrlGibt die gesamte URL-Anforderungszeichenfolge zurück.http://localhost:5000/user/[email protected]
HostGibt den Host der Anfrage zurück.localhost
AuthorityGibt den Host und Port der Anfrage zurück.localhost:5000
QueryStringGibt die Abfrage der Anfrage zurück.[email protected]
QueryGibt die Abfrage der Anfrage in einer benannten Wertsammlung zurück.{StringValueCollection object}
IsSecureBestimmt, ob die Anfrage SSL verwendet (true) oder nicht (false).false

Sie können auch die Property HttpRequest.Uri 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 MethodeVerwendung
RequestIdEin eindeutiger Bezeichner für die Anfrage. Aktivieren Sie IncludeRequestIdHeader, um ihn als X-Request-Id zurückzugeben.
RequestedAtDer Zeitpunkt, zu dem Sisk das Anforderungsobjekt erstellt hat.
RemoteAddressDie vom Verbindungsaufbau ermittelte Client-Adresse oder aus Ihrem ForwardingResolver.
CultureDie aus Accept-Language ermittelte beste Kultur, mit Rückfall zur aktuellen Kultur.
DisconnectTokenEin Abbruch‑Token, das ausgelöst wird, wenn der Client die Verbindung trennt, sofern die konfigurierte HTTP‑Engine dies unterstützt.
BagEin typisierter Schlüssel/Wert‑Speicher, der über Anforderungs‑Handler und die Routinenaktion hinweg geteilt wird.
GetRawHttpRequestEine 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:

C#
// 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<byte> bodyMemory = await request.GetBodyContentsAsync();

Es ist außerdem möglich, mit den Properties HasContents und IsContentAvailable 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 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 als auch für HttpRequest.RawBody gelten. Diese Eigenschaften kopieren den gesamten Eingabestream in einen lokalen Puffer der Größe von HttpRequest.ContentLength.

Der Anforderungsinhalt ist durch HttpServerConfiguration.MaximumContentLength 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, Body oder den von GetRequestStream zurückgegebenen Stream wirft eine InsufficientMemoryException, was zu einem 500 Internal Server Error führt, sofern Ihr Code oder Router.CallbackErrorHandler dies nicht abfängt.
  • Das gleiche Limit ist die maximale Größe einer empfangenen WebSocket Nachricht.

Setzen Sie die Property auf 0, um das Limit zu entfernen:

C#
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 (etwa 2 GB) puffern und werfen eine Ausnahme, wenn Sie versuchen, darauf zuzugreifen. Größere Inhalte können weiterhin über Streaming verarbeitet werden.

Hinweis

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”.

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 und greifen standardmäßig auf HttpRequest.DefaultJsonSerializerOptions zurück.

C#
public record CreateUserRequest(string Name, string Email);

router.MapPost("/users", (HttpRequest request) =>
{
    CreateUserRequest? body = request.GetJsonContent<CreateUserRequest>();
    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:

C#
router.MapPost("/users", async (HttpRequest request) =>
{
    CreateUserRequest? body =
        await request.GetJsonContentAsync<CreateUserRequest>(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 übergeben:

C#
var options = new JsonSerializerOptions(JsonSerializerDefaults.Web)
{
    PropertyNameCaseInsensitive = true
};

UserDto? user = request.GetJsonContent<UserDto>(options);

Für Native‑AOT‑ oder trim‑sensible Anwendungen verwenden Sie die JsonTypeInfo<T>‑Überladung, die von einem JsonSerializerContext erzeugt wird:

C#
[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 über die statische Methode HttpContext.GetCurrentContext() erhalten. Diese Methode gibt den Kontext der gerade in dem aktuellen Thread verarbeiteten Anfrage zurück.

C#
HttpContext context = HttpContext.GetCurrentContext();

Protokollmodus #

Die Property HttpContext.LogMode 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.

C#
// Logging für diese Anfrage deaktivieren
context.LogMode = LogOutputMode.None;

Request‑Bag #

Das RequestBag 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.

Tipp

Diese Eigenschaft ist auch über die HttpRequest.Bag Eigenschaft zugänglich.

Middleware/AuthenticateUserRequestHandler.csC#
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:

Controller/MyController.csC#
public class MyController
{
    [RouteGet("/")]
    [RequestHandler<AuthenticateUserRequestHandler>]
    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.

Middleware/Authenticate.csC#
public class Authenticate : RequestHandler
{
    public override HttpResponse? Execute(HttpRequest request, HttpContext context)
    {
        request.Bag.Set<User>(authUser);
    }
}
Controller/MyController.csC#
[RouteGet("/")]
[RequestHandler<Authenticate>]
public static HttpResponse GetUser(HttpRequest request)
{
    var user = request.Bag.Get<User>();
    ...
}

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. Sie gibt ein HttpContextInterlocked Objekt mit atomaren Operationen über benannte Werte des Request‑Bags zurück:

MethodeBeschreibung
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.

C#
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");
});

Hinweis

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 wie im folgenden Beispiel erhalten:

Controller/Auth.csC#
[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:

C#
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.

Controller/Auth.csC#
[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, wenn die Route asynchron ist:

C#
var multipartFormDataObjects =
    await request.GetMultipartFormContentAsync(request.DisconnectToken);

Weitere Informationen zu Sisk‑Multipart‑Form‑Objekten 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 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.

C#
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 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, wodurch Datenblöcke als Stream gesendet und die Verbindung zwischen Server und Client aufrechterhalten werden kann.

Der Aufruf der Methode HttpRequest.GetEventSource 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‑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 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.

sse-example.htmlHTML
<html>
    <body>
        <b>Fruits:</b>
        <ul></ul>
    </body>
    <script>
        const evtSource = new EventSource('http://localhost:5555/event-source');
        const eventList = document.querySelector('ul');
        
        evtSource.onmessage = (e) => {
            const newElement = document.createElement("li");

            newElement.textContent = `message: ${e.data}`;
            eventList.appendChild(newElement);

            if (e.data == "Tomato") {
                evtSource.close();
            }
        }
    </script>
</html>

Und das fortlaufende Senden der Nachrichten an den Client:

Controller/MyController.csC#
public class MyController
{
    [RouteGet("/event-source")]
    public async Task<HttpResponse> 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 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 aktiviert werden.

Tippen, um die Dokumentation und die API-Referenz zu durchsuchen.