Sisk

Solicitudes

Esta página fue traducida automáticamente del inglés. Leer el original

Las solicitudes son estructuras que representan un mensaje de petición HTTP. El objeto HttpRequest contiene funciones útiles para manejar mensajes HTTP a lo largo de su aplicación.

Una petición HTTP se compone del método, la ruta, la versión, los encabezados y el cuerpo.

En este documento, le enseñaremos cómo obtener cada uno de estos elementos.

Obtención del método de la solicitud #

Para obtener el método de la solicitud recibida, puede usar la propiedad Method:

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

Esta propiedad devuelve el método de la solicitud representado por un objeto HttpMethod .

Nota

A diferencia de los métodos de ruta, esta propiedad no sirve al elemento RouteMethod.Any. En su lugar, devuelve el método real de la solicitud.

Obtención de los componentes de la URL de la solicitud #

Puede obtener varios componentes de una URL a través de ciertas propiedades de una solicitud. Para este ejemplo, consideremos la URL:

http://localhost:5000/user/[email protected]
Nombre del componenteDescripciónValor del componente
PathObtiene la ruta de la solicitud./user/login
FullPathObtiene la ruta de la solicitud y la cadena de consulta./user/[email protected]
FullUrlObtiene la cadena completa de la URL de la solicitud.http://localhost:5000/user/[email protected]
HostObtiene el host de la solicitud.localhost
AuthorityObtiene el host y el puerto de la solicitud.localhost:5000
QueryStringObtiene la cadena de consulta de la solicitud.[email protected]
QueryObtiene la consulta de la solicitud en una colección de valores con nombre.{StringValueCollection object}
IsSecureDetermina si la solicitud está usando SSL (true) o no (false).false

También puede optar por usar la propiedad HttpRequest.Uri, que incluye todo lo anterior en un solo objeto.

Metadatos de la solicitud y cancelación #

Sisk también adjunta metadatos operacionales a cada solicitud. Estas propiedades son útiles para registros, rastreo, localización, diagnóstico y operaciones de larga duración:

Propiedad o métodoUso
RequestIdUn identificador único para la solicitud. Habilite IncludeRequestIdHeader para devolverlo como X-Request-Id.
RequestedAtEl momento en que Sisk creó el objeto de solicitud.
RemoteAddressLa dirección del cliente resuelta a partir de la conexión, o de su ForwardingResolver.
CultureLa mejor cultura resuelta a partir de Accept-Language, retrocediendo a la cultura actual.
DisconnectTokenUn token de cancelación que se activa cuando el cliente se desconecta, cuando es soportado por el motor HTTP configurado.
BagUn almacén tipado de clave/valor compartido entre los manejadores de solicitud y la acción de ruta.
GetRawHttpRequestUna representación textual de la solicitud para diagnóstico.

Obtención del cuerpo de la solicitud #

Algunas solicitudes incluyen cuerpo, como formularios, archivos o transacciones API. Puede obtener el cuerpo de una solicitud a través de la propiedad:

C#
// obtiene el cuerpo de la solicitud como una cadena, usando la codificación de la solicitud como codificador
string body = request.Body;

// o lo obtiene en un arreglo de bytes
byte[] bodyBytes = request.RawBody;

// o también, puede transmitirlo.
Stream requestStream = request.GetRequestStream();

// o leer el cuerpo de forma asíncrona
Memory<byte> bodyMemory = await request.GetBodyContentsAsync();

También es posible determinar si hay un cuerpo en la solicitud y si está cargado con las propiedades HasContents, que determina si la solicitud tiene contenido y IsContentAvailable , que indica que el servidor HTTP recibió completamente el contenido del punto remoto.

No es posible leer el contenido de la solicitud a través de GetRequestStream más de una vez. Si lee con este método, los valores en RawBody y Body tampoco estarán disponibles. No es necesario disponer del flujo de solicitud en el contexto de la solicitud, ya que se dispone al final de la sesión HTTP en la que se crea. Además, puede usar la propiedad HttpRequest.RequestEncoding para obtener la mejor codificación y decodificar la solicitud manualmente.

El servidor tiene límites para leer el contenido de la solicitud, lo que se aplica tanto a HttpRequest.Body como a HttpRequest.RawBody. Estas propiedades copian todo el flujo de entrada a un búfer local del mismo tamaño que HttpRequest.ContentLength.

El contenido de la solicitud está limitado por HttpServerConfiguration.MaximumContentLength, que por defecto es 100 MB desde Sisk 1.7 (era 0, ilimitado, en versiones anteriores). El límite se aplica de la siguiente manera:

  • Una solicitud que declara un Content-Length mayor que el límite recibe una respuesta 413 Content Too Large antes de que se invoque la ruta.
  • Un cuerpo fragmentado o transmitido sin una longitud declarada se verifica mientras se lee. Leer más allá del límite mediante RawBody, Body o el flujo devuelto por GetRequestStream lanza una InsufficientMemoryException, lo que resulta en un 500 Internal Server Error a menos que su código o Router.CallbackErrorHandler lo maneje.
  • El mismo límite es el tamaño máximo de un mensaje WebSocket recibido.

Establezca la propiedad en 0 para eliminar el límite:

C#
using var app = HttpServer.CreateBuilder()
    .UseConfiguration(config => config.MaximumContentLength = 0)
    .Build();

Incluso sin un límite configurado, Body y RawBody no pueden almacenar en búfer contenido mayor que Array.MaxLength (aproximadamente 2 GB) y lanzan una excepción cuando intenta acceder a ellos. Puede seguir manejando contenido mayor mediante transmisión.

Nota

Aunque Sisk lo permite, siempre es una buena idea seguir la Semántica HTTP para crear su aplicación y no obtener o servir contenido en métodos que no lo permiten. Lea sobre RFC 9110 “HTTP Semantics”.

Lectura de solicitudes JSON #

Para APIs JSON, prefiera los ayudantes JSON incorporados en lugar de leer Body y deserializar manualmente. Utilizan System.Text.Json y por defecto usan HttpRequest.DefaultJsonSerializerOptions.

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);
});

Use la sobrecarga asíncrona cuando ya esté en una ruta async o quiera que la cancelación de la solicitud detenga la deserialización:

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);
});

Puede pasar JsonSerializerOptions personalizados para un punto final específico:

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

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

Para aplicaciones Native AOT o sensibles al recorte, use la sobrecarga JsonTypeInfo<T> generada por un JsonSerializerContext:

C#
[JsonSerializable(typeof(CreateUserRequest))]
public partial class AppJsonSerializerContext : JsonSerializerContext
{
}

CreateUserRequest? body =
    await request.GetJsonContentAsync(
        AppJsonSerializerContext.Default.CreateUserRequest,
        request.DisconnectToken);

La misma regla de lectura única se aplica a los ayudantes JSON: después de que Sisk lea el flujo de la solicitud mediante GetJsonContent, GetJsonContentAsync, Body o RawBody, no podrá consumir más tarde el mismo cuerpo mediante GetRequestStream().

Obtención del contexto de la solicitud #

El HTTP Context es un objeto exclusivo de Sisk que almacena información del servidor HTTP, ruta, router y manejador de solicitudes. Puede usarlo para organizarse en un entorno donde estos objetos son difíciles de organizar.

Puede obtener el HttpContext que se está ejecutando actualmente usando el método estático HttpContext.GetCurrentContext(). Este método devuelve el contexto de la solicitud que se está procesando en el hilo actual.

C#
HttpContext context = HttpContext.GetCurrentContext();

Modo de registro #

La propiedad HttpContext.LogMode le permite controlar el comportamiento de registro para la solicitud actual. Puede habilitar o deshabilitar el registro para solicitudes específicas, sobrescribiendo la configuración predeterminada del servidor.

C#
// Deshabilitar el registro para esta solicitud
context.LogMode = LogOutputMode.None;

Bolsa de solicitud #

El objeto RequestBag contiene información almacenada que se pasa de un manejador de solicitud a otro punto, y puede ser consumida en el destino final. Este objeto también puede ser usado por manejadores de solicitud que se ejecutan después del callback de la ruta.

Consejo

Esta propiedad también es accesible mediante la propiedad HttpRequest.Bag.

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);
        }
    }
}

El manejador de solicitud anterior definirá AuthenticatedUser en la bolsa de solicitud, y podrá ser consumido más tarde en el callback final:

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

También puede usar los métodos auxiliares Bag.Set() y Bag.Get() para obtener o establecer objetos por sus singletons de tipo.

La clase TypedValueDictionary también proporciona los métodos GetValue y SetValue para mayor control.

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>();
    ...
}

Valores atómicos del contexto #

La bolsa de solicitud no está sincronizada. Cuando valores numéricos en la bolsa pueden ser modificados por código concurrente durante la misma solicitud, como tareas paralelas iniciadas por la ruta, use HttpContext.Interlocked. Devuelve un objeto HttpContextInterlocked con operaciones atómicas sobre valores nombrados de la bolsa de solicitud:

MétodoDescripción
Add(name, value)Agrega value al valor almacenado, o almacena value cuando name no está establecido.
Exchange(name, value)Reemplaza el valor almacenado y devuelve el anterior, o value cuando name no estaba establecido.
CompareExchange(name, value, comparand, notFound = -1)Reemplaza el valor almacenado con value solo cuando es igual a comparand, y devuelve el valor original, o notFound cuando name no está establecido.
Inspect(name)Devuelve el valor almacenado, o null cuando name no está establecido.

Los valores se almacenan como double en la bolsa de solicitud. Las sobrecargas que reciben valores int o long los convierten a 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");
});

Nota

Las operaciones son atómicas solo con respecto a otras llamadas a Interlocked. Leer o escribir la misma clave directamente a través de RequestBag no está sincronizado con ellas.

Obtención de datos de formulario #

Puede obtener valores de datos de formulario en una StringKeyStoreCollection con el ejemplo a continuación:

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))
    {
        ...
    }
}

La versión async es útil cuando el cuerpo de la solicitud puede ser grande o cuando desea soporte de cancelación:

C#
var form = await request.GetFormContentAsync(request.DisconnectToken);

Obtención de datos de formulario multipart #

La solicitud HTTP de Sisk le permite obtener contenidos multipart cargados, como archivos, campos de formulario o cualquier contenido binario.

Controller/Auth.csC#
[RoutePost("/upload-contents")]
public HttpResponse Index(HttpRequest request)
{
    // el siguiente método lee toda la entrada de la solicitud en un
    // arreglo de MultipartObjects
    var multipartFormDataObjects = request.GetMultipartFormContent();
    
    foreach (MultipartObject uploadedObject in multipartFormDataObjects)
    {
        // El nombre del archivo proporcionado por los datos de formulario multipart.
        // Se devuelve null si el objeto no es un archivo.
        Console.WriteLine("File name       : " + uploadedObject.Filename);

        // El nombre del campo del objeto de datos de formulario multipart.
        Console.WriteLine("Field name      : " + uploadedObject.Name);

        // La longitud del contenido del objeto de datos de formulario multipart.
        Console.WriteLine("Content length  : " + uploadedObject.ContentLength);

        // Determina el formato de imagen basado en el encabezado del archivo para cada
        // tipo de contenido conocido. Si el contenido no es un formato de archivo común reconocido,
        // este método devolverá MultipartObjectCommonFormat.Unknown
        Console.WriteLine("Common format   : " + uploadedObject.GetCommonFileFormat());
    }
}

Use GetMultipartFormContentAsync cuando la ruta sea asíncrona:

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

Puede leer más sobre los objetos de formulario multipart de Sisk y sus métodos, propiedades y funcionalidades.

Detección de la desconexión del cliente #

Desde la versión v1.15 de Sisk, el framework proporciona un token de cancelación a través de HttpRequest.DisconnectToken. Cuando el motor HTTP configurado soporta la detección de desconexión, este token se cancela cuando la conexión del cliente se cierra antes de que la respuesta se complete. Esto es útil para detener operaciones de larga duración cuando el cliente ya no está esperando el resultado.

C#
router.MapGet("/connect", async (HttpRequest req) =>
{
    // obtiene el token de desconexión de la solicitud
    var dc = req.DisconnectToken;

    await LongOperationAsync(dc);

    return new HttpResponse();
});

Este token no es compatible con todos los motores HTTP, y cada uno requiere una implementación.

El motor predeterminado de Sisk, basado en System.Net.HttpListener, no soporta la detección de desconexión del cliente. Cuando su aplicación usa el motor predeterminado, DisconnectToken es CancellationToken.None; en la práctica, es un token que no se cancela y debe tratarse como no disponible.

El motor Cadente soporta DisconnectToken. Si su ruta depende de la cancelación consciente de la desconexión, use Cadente u otro motor que implemente explícitamente este comportamiento. Incluso con un motor compatible, la cancelación es cooperativa: pase el token a APIs async y verifíquelo en su propio trabajo de larga duración.

Soporte de eventos enviados por el servidor #

Sisk soporta Server-sent events, lo que permite enviar fragmentos como un flujo y mantener viva la conexión entre el servidor y el cliente.

Llamar al método HttpRequest.GetEventSource pondrá el HttpRequest en su estado de escucha. A partir de esto, el contexto de esta solicitud HTTP no esperará un HttpResponse ya que se superpondrán los paquetes enviados por los eventos del lado del servidor.

Después de enviar todos los paquetes, el callback debe devolver el método Close, que enviará la respuesta final al cliente e indicará que la transmisión ha finalizado.

No es posible predecir la longitud total de todos los paquetes que se enviarán, por lo que no es posible determinar el fin de la conexión con el encabezado Content-Length.

Según la mayoría de los navegadores, los eventos del lado del servidor no soportan el envío de encabezados HTTP ni métodos distintos al GET. Por lo tanto, tenga cuidado al usar manejadores de solicitud con peticiones de tipo event‑source que requieran encabezados específicos, ya que probablemente no los tendrán.

Además, la mayoría de los navegadores reinician los flujos si el método EventSource.close no se llama del lado del cliente después de recibir todos los paquetes, lo que provoca un procesamiento infinito adicional en el servidor. Para evitar este tipo de problema, es común enviar un paquete final que indique que la fuente de eventos ha terminado de enviar todos los paquetes.

El siguiente ejemplo muestra cómo el navegador puede comunicarse con el servidor que soporta eventos del lado del servidor.

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>

Y enviar progresivamente los mensajes al cliente:

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();
    }
}

Al ejecutar este código, esperamos un resultado similar a este:

Resolución de IPs y hosts proxied #

Sisk puede usarse con proxies, y por lo tanto las direcciones IP pueden ser reemplazadas por el punto final del proxy en la transacción de un cliente al proxy.

Puede definir sus propios resolutores en Sisk con los forwarding resolvers.

Codificación de encabezados #

La codificación de encabezados puede ser un problema para algunas implementaciones. En Windows, los encabezados UTF‑8 no son compatibles, por lo que se usa ASCII. Sisk tiene un conversor de codificación incorporado, que puede ser útil para decodificar encabezados codificados incorrectamente.

Esta operación es costosa y está deshabilitada por defecto, pero puede habilitarse con HttpServerConfiguration.NormalizeHeadersEncodings.

Escribe para buscar en la documentación y en la referencia de la API.