# Sisk Framework documentation > Sisk is a lightweight, agnostic and robust .NET web development framework. This file contains the complete Sisk documentation (Español). The API reference is not included; read https://docs.sisk-framework.org/api/index.md for the type index. --- # Comenzando Source: https://docs.sisk-framework.org/es/docs/getting-started.html ¡Bienvenido a la documentación de Sisk! Sisk es un framework HTTP ligero y de código abierto para .NET. Puedes usarlo para crear un servicio web independiente, incrustar un módulo HTTP dentro de una aplicación existente, o ejecutar un servicio detrás de un proxy inverso con solo la configuración que necesitas. Los valores de Sisk incluyen transparencia del código, modularidad, rendimiento y escalabilidad. Puede manejar diferentes estilos de aplicación, incluidos APIs RESTful, servicios JSON‑RPC, WebSockets, Server‑Sent Events y servir archivos estáticos. Sus principales características incluyen: | Recurso | Descripción | | ------- | ----------- | | [Routing](https://docs.sisk-framework.org/es/docs/fundamentals/routing.md) | Un enrutador de rutas que soporta prefijos, métodos personalizados, variables de ruta, convertidores de valores y más. | | [Request Handlers](https://docs.sisk-framework.org/es/docs/fundamentals/request-handlers.md) | También conocidos como *middlewares*, proporcionan una interfaz para crear tus propios manejadores de solicitud que actúan antes o después de una acción. | | [Compression](https://docs.sisk-framework.org/es/docs/fundamentals/responses.md#gzip-deflate-and-brotli-compression) | Comprime fácilmente el contenido de tus respuestas con Sisk. | | [Web sockets](https://docs.sisk-framework.org/es/docs/features/websockets.md) | Proporciona rutas que aceptan websockets completos, para leer y escribir al cliente. | | [Server-sent events](https://docs.sisk-framework.org/es/docs/features/server-sent-events.md) | Permite el envío de eventos del servidor a clientes que soportan el protocolo SSE. | | [Logging](https://docs.sisk-framework.org/es/docs/features/logging.md) | Registro simplificado. Registra errores, accesos, define rotación de logs por tamaño, múltiples flujos de salida para el mismo log, y más. | | [Multi-host](https://docs.sisk-framework.org/es/docs/advanced/multi-host-setup.md) | Tener un servidor HTTP para varios puertos, y cada puerto con su propio enrutador, y cada enrutador con su propia aplicación. | | [Server handlers](https://docs.sisk-framework.org/es/docs/advanced/http-server-handlers.md) | Extiende tu propia implementación del servidor HTTP. Personaliza con extensiones, mejoras y nuevas funcionalidades. | ## Primeros pasos Sisk puede ejecutarse en cualquier entorno .NET. En esta guía, te enseñaremos cómo crear una aplicación Sisk usando .NET. Si aún no lo has instalado, descarga el SDK desde [aquí](https://dotnet.microsoft.com/en-us/download/dotnet/7.0). En este tutorial, cubriremos cómo crear una estructura de proyecto, recibir una solicitud, obtener un parámetro de URL y enviar una respuesta. Esta guía se centrará en construir un servidor simple usando C#. También puedes usar tu lenguaje de programación favorito. > [!NOTE] > Puede que te interese un proyecto de inicio rápido. Consulta [este repositorio](https://github.com/sisk-http/quickstart) para más información. ## Creando un proyecto Llamemos a nuestro proyecto "My Sisk Application". Una vez que tengas .NET configurado, puedes crear tu proyecto con el siguiente comando: ```bash dotnet new console -n my-sisk-application ``` Luego, navega al directorio de tu proyecto e instala Sisk usando la herramienta de utilidad de .NET: ```bash cd my-sisk-application dotnet add package Sisk.HttpServer ``` Puedes encontrar formas adicionales de instalar Sisk en tu proyecto [aquí](https://www.nuget.org/packages/Sisk.HttpServer/). Ahora, creemos una instancia de nuestro servidor HTTP. Para este ejemplo, lo configuraremos para escuchar en el puerto 5000. ## Construyendo el servidor HTTP Sisk te permite construir tu aplicación paso a paso manualmente, ya que enruta al objeto HttpServer. Sin embargo, esto puede no ser muy conveniente para la mayoría de los proyectos. Por lo tanto, podemos usar el método builder, que facilita poner nuestra aplicación en marcha. ```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 importante entender cada componente vital de Sisk. Más adelante en este documento, aprenderás más sobre cómo funciona Sisk. ## Configuración manual (avanzada) Puedes aprender cómo funciona cada mecanismo de Sisk en [esta sección](https://docs.sisk-framework.org/es/docs/advanced/manual-setup.md) de la documentación, que explica el comportamiento y las relaciones entre HttpServer, Router, ListeningPort y otros componentes. --- # Instalación Source: https://docs.sisk-framework.org/es/docs/installing.html Puedes instalar Sisk a través de NuGet, la CLI de dotnet o [otras opciones](https://www.nuget.org/packages/Sisk.HttpServer/). Puedes configurar fácilmente tu entorno Sisk ejecutando este comando en tu consola de desarrollo: ```sh dotnet add package Sisk.HttpServer ``` Este comando instalará la última versión de Sisk en tu proyecto. Sisk 1.7 está dirigido a .NET 8, .NET 9 y .NET 10. Los paquetes del [Cadente engine](https://docs.sisk-framework.org/es/docs/cadente.md) están dirigidos solo a .NET 9 y .NET 10. --- # Soporte de AOT Nativo Source: https://docs.sisk-framework.org/es/docs/native-aot.html [.NET Native AOT](https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/) permite la publicación de aplicaciones .NET nativas que son autosuficientes y no requieren que el tiempo de ejecución .NET esté instalado en el host de destino. Además, Native AOT proporciona beneficios como: - Aplicaciones mucho más pequeñas - Inicialización significativamente más rápida - Menor consumo de memoria Sisk Framework, por su naturaleza explícita, permite el uso de Native AOT para casi todas sus características sin requerir rework en el código fuente para adaptarlo a Native AOT. ## Características no compatibles Sin embargo, Sisk utiliza la reflexión, aunque mínima, para algunas características. Las características mencionadas a continuación pueden estar parcialmente disponibles o completamente no disponibles durante la ejecución de código nativo: - [Exploración automática de módulos](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.AutoScanModules.md) del enrutador: este recurso escanea los tipos incrustados en el ensamblado en ejecución y registra los tipos que son [módulos del enrutador](https://docs.sisk-framework.org/es/docs/fundamentals/routing.md). Este recurso requiere tipos que pueden ser excluidos durante el recorte del ensamblado. Todas las demás características son compatibles con AOT en Sisk. Es común encontrar uno o otro método que genera una advertencia de AOT, pero el mismo, si no se menciona aquí, tiene una sobrecarga que indica el paso de un tipo, parámetro o información de tipo que ayuda al compilador de AOT a compilar el objeto. --- # Desplegando su aplicación Sisk Source: https://docs.sisk-framework.org/es/docs/deploying.html El proceso de desplegar una aplicación Sisk consiste en publicar su proyecto en producción. Aunque el proceso es relativamente sencillo, vale la pena señalar detalles que pueden ser letales para la seguridad y la estabilidad de la infraestructura del despliegue. Idealmente, debería estar listo para desplegar su aplicación en la nube, después de realizar todas las pruebas posibles para que su aplicación esté preparada. ## Publicando su aplicación Publicar su aplicación o servicio Sisk consiste en generar binarios listos y optimizados para producción. En este ejemplo, compilaremos los binarios para producción para ejecutarlos en una máquina que tenga el .NET Runtime instalado. Necesitará el .NET SDK instalado en su máquina para compilar su aplicación, y el .NET Runtime instalado en el servidor de destino para ejecutar su aplicación. Puede aprender cómo instalar .NET Runtime en su servidor Linux [aquí](https://learn.microsoft.com/en-us/dotnet/core/install/linux), [Windows](https://learn.microsoft.com/en-us/dotnet/core/install/windows?tabs=net70) y [Mac OS](https://learn.microsoft.com/en-us/dotnet/core/install/macos). En la carpeta donde se encuentra su proyecto, abra una terminal y use el comando .NET publish: ```shell $ dotnet publish -r linux-x64 -c Release ``` Esto generará sus binarios dentro de `bin/Release/publish/linux-x64`. > [!NOTE] > Si su aplicación usa un [archivo de configuración portable](https://docs.sisk-framework.org/es/docs/extensions/service-providers.md), debe copiar su `service-config.json` al servidor host junto con todos los binarios generados por `dotnet publish`. Puede dejar el archivo preconfigurado, con variables de entorno, puertos y hosts de escucha, y configuraciones de servidor adicionales. El siguiente paso es llevar estos archivos al servidor donde se alojará su aplicación. Después de eso, otorgue permisos de ejecución a su archivo binario. En este caso, consideremos que el nombre de nuestro proyecto es "my-app": ```shell $ cd /home/htdocs $ chmod +x my-app $ ./my-app ``` Después de ejecutar su aplicación, verifique si produce algún mensaje de error. Si no produce ninguno, es porque su aplicación está en ejecución. En este punto, probablemente no será posible acceder a su aplicación desde la red externa fuera de su servidor, ya que reglas de acceso como el firewall no han sido configuradas. Consideraremos esto en los siguientes pasos. Debe tener la dirección del host virtual donde su aplicación está escuchando. Esto se establece manualmente en la aplicación y depende de cómo esté instanciando su servicio Sisk. Si **no** está usando un archivo de configuración portable, debería encontrarlo donde definió su instancia de HttpServer: ```cs HttpServer server = HttpServer.Emit(5000, out HttpServerConfiguration config, out var host, out var router); // sisk debería escuchar en http://localhost:5000/ ``` Asociando un ListeningHost manualmente: ```cs config.ListeningHosts.Add(new ListeningHost("https://localhost:5000/", router)); ``` O si está usando un archivo de configuración portable, en su service-config.json: ```json { "Server": { }, "ListeningHost": { "Ports": [ "http://localhost:5000/" ] } } ``` A partir de esto, podemos crear un proxy inverso para escuchar su servicio y hacer que el tráfico esté disponible a través de la red abierta. ## Proxyando su aplicación Proxyar su servicio significa no exponer directamente su servicio Sisk a una red externa. Esta práctica es muy común en despliegues de servidores porque: - Le permite asociar un certificado SSL en su aplicación; - Crear reglas de acceso antes de acceder al servicio y evitar sobrecargas; - Controlar el ancho de banda y los límites de solicitudes; - Separar balanceadores de carga para su aplicación; - Prevenir daños de seguridad a la infraestructura fallida. Puede servir su aplicación a través de un proxy inverso como [Nginx](https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/linux-nginx?view=aspnetcore-7.0&tabs=linux-ubuntu#install-nginx) o [Apache](https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/linux-apache?view=aspnetcore-7.0), o puede usar un túnel http-over-dns como [Cloudflared](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/install-and-setup/tunnel-guide/). Además, recuerde resolver correctamente los encabezados de reenvío de su proxy para obtener la información de sus clientes, como la dirección IP y el host, a través de [resolvers de reenvío](https://docs.sisk-framework.org/es/docs/advanced/forwarding-resolvers.md). El siguiente paso después de crear su túnel, configurar el firewall y tener su aplicación en ejecución, es crear un servicio para su aplicación. > [!NOTE] > Usar certificados SSL directamente en el servicio Sisk en sistemas que no sean Windows no es posible. Esto se debe a la implementación de HttpListener, que es el módulo central de cómo se gestiona la cola HTTP en Sisk, y esta implementación varía de un sistema operativo a otro. Puede usar SSL en su servicio Sisk si [asocia un certificado con el host virtual mediante IIS](https://learn.microsoft.com/en-us/iis/manage/configuring-security/how-to-set-up-ssl-on-iis). Para otros sistemas, se recomienda encarecidamente usar un proxy inverso. ## Creando un servicio Crear un servicio hará que su aplicación esté siempre disponible, incluso después de reiniciar la instancia del servidor o tras un fallo irrecuperable. En este sencillo tutorial, usaremos el contenido del tutorial anterior como ejemplo para mantener su servicio siempre activo. 1. Acceda a la carpeta donde se encuentran los archivos de configuración del servicio: ```sh cd /etc/systemd/system ``` 2. Cree su archivo `my-app.service` e incluya el contenido: ```ini {title="my-app.service"} [Unit] Description= [Service] # establece el usuario que lanzará el servicio User= # la ruta ExecStart no es relativa al WorkingDirectory. # establécela como la ruta completa al archivo ejecutable WorkingDirectory=/home/htdocs ExecStart=/home/htdocs/my-app # configura el servicio para que siempre se reinicie en caso de fallo Restart=always RestartSec=3 [Install] WantedBy=multi-user.target ``` 3. Reinicie el módulo de gestión de servicios: ```sh $ sudo systemctl daemon-reload ``` 4. Inicie el servicio recién creado usando el nombre del archivo que configuró y verifique si está en ejecución: ```sh $ sudo systemctl start my-app $ sudo systemctl status my-app ``` 5. Ahora, si su aplicación está en ejecución ("Active: active"), habilite su servicio para que continúe ejecutándose después de un reinicio del sistema: ```sh $ sudo systemctl enable my-app ``` Ahora está listo para lanzar y presentar su aplicación Sisk a todos. --- # Trabajando con SSL Source: https://docs.sisk-framework.org/es/docs/ssl.html Trabajar con SSL en desarrollo puede ser necesario cuando se trabaja en contextos que requieren seguridad, como la mayoría de los escenarios de desarrollo web. Sisk funciona sobre HttpListener, que no soporta HTTPS nativo, solo HTTP. Sin embargo, existen soluciones alternativas que permiten trabajar con SSL en Sisk. Véalas a continuación: ## A través de Sisk.Cadente.CoreEngine - Disponible en: Linux, macOS, Windows - Esfuerzo: fácil Es posible usar el motor experimental [**Cadente**](https://docs.sisk-framework.org/es/docs/cadente.md) en proyectos Sisk, sin requerir configuración adicional en el equipo o en el proyecto. Necesitarás instalar el paquete `Sisk.Cadente.CoreEngine` en tu proyecto para poder usar el servidor Cadente en el servidor Sisk. Para configurar SSL, puedes usar los métodos `UseSsl` y `UseEngine` del constructor: ```csharp using var http = HttpServer.CreateBuilder() .UseEngine() .UseSsl(CertificateHelper.CreateTrustedDevelopmentCertificate("localhost")) ``` > Nota: este paquete sigue en fase experimental. ## A través de IIS en Windows - Disponible en: Windows - Esfuerzo: medio Si estás en Windows, puedes usar IIS para habilitar SSL en tu servidor HTTP. Para que esto funcione, es aconsejable que sigas [este tutorial](https://docs.sisk-framework.org/es/docs/registering-namespace.md) de antemano si deseas que tu aplicación escuche en un host distinto a "localhost." Para que funcione, debes instalar IIS a través de las características de Windows. IIS está disponible de forma gratuita para usuarios de Windows y Windows Server. Para configurar SSL en tu aplicación, ten listo el certificado SSL, aunque sea autofirmado. Luego, puedes consultar [cómo configurar SSL en IIS 7 o superior](https://learn.microsoft.com/en-us/iis/manage/configuring-security/how-to-set-up-ssl-on-iis). ## A través de mitmproxy - Disponible en: Linux, macOS, Windows - Esfuerzo: fácil **mitmproxy** es una herramienta de proxy de interceptación que permite a desarrolladores y evaluadores de seguridad inspeccionar, modificar y registrar el tráfico HTTP y HTTPS entre un cliente (como un navegador web) y un servidor. Puedes usar la utilidad **mitmdump** para iniciar un proxy SSL inverso entre tu cliente y tu aplicación Sisk. 1. En primer lugar, instala [mitmproxy](https://mitmproxy.org/) en tu máquina. 2. Inicia tu aplicación Sisk. Para este ejemplo, usaremos el puerto 8000 como el puerto HTTP inseguro. 3. Inicia el servidor mitmproxy para escuchar en el puerto seguro 8001: ```sh mitmdump --mode reverse:http://localhost:8000/ -p 8001 ``` ¡Y ya estás listo! Ya puedes acceder a tu aplicación a través de `https://localhost:8001/`. Tu aplicación no necesita estar ejecutándose para que inicies `mitmdump`. Alternativamente, puedes agregar una referencia al [asistente de mitmproxy](https://github.com/sisk-http/core/tree/main/extensions/Sisk.Helpers.mitmproxy) en tu proyecto. Esto aún requiere que mitmproxy esté instalado en tu computadora. ## Paquete Sisk.SslProxy (descontinuado) > [!CAUTION] > > El paquete Sisk.SslProxy está descontinuado y obsoleto en NuGet. Ya no compila contra la API actual de Cadente. No lo uses: utiliza el soporte SSL nativo del [motor Cadente](#through-the-siskcadentecoreengine) en su lugar. --- # Cadente Source: https://docs.sisk-framework.org/es/docs/cadente.html Cadente es una implementación experimental gestionada de un listener HTTP/1.1 para Sisk. Sirve como reemplazo del `System.Net.HttpListener` predeterminado, ofreciendo mayor control y flexibilidad, especialmente en plataformas que no son Windows. ## Visión general Por defecto, Sisk utiliza `HttpListener` (de `System.Net`) como su motor subyacente de servidor HTTP. Mientras que `HttpListener` es estable y de buen rendimiento en Windows (donde usa el controlador kernel‑mode HTTP.sys), su implementación en Linux y macOS es gestionada y históricamente ha tenido limitaciones, como la falta de soporte nativo de SSL (requiriendo un proxy inverso como Nginx) y características de rendimiento variables. Cadente pretende resolver estos problemas proporcionando un servidor HTTP/1.1 totalmente gestionado escrito en C#. Sus objetivos principales son: - **Soporte nativo de SSL:** Funciona en todas las plataformas sin necesidad de proxies externos ni configuraciones complejas. - **Consistencia multiplataforma:** Comportamiento idéntico en Windows, Linux y macOS. - **Rendimiento:** Diseñado como una alternativa de alto rendimiento al `HttpListener` gestionado. - **Independencia:** Desacoplado de `System.Net.HttpListener`, aislando a Sisk de posibles deprecaciones futuras o falta de mantenimiento de ese componente en .NET. > [!WARNING] > **Estado experimental** > > Cadente se encuentra actualmente en una fase experimental (Beta). No se recomienda aún para entornos de producción críticos. La API y el comportamiento pueden cambiar. ## Instalación Cadente está disponible como un paquete separado. Para usarlo con Sisk, necesitas el paquete `Sisk.Cadente.CoreEngine`. ```bash dotnet add package Sisk.Cadente.CoreEngine ``` `Sisk.Cadente` y `Sisk.Cadente.CoreEngine` apuntan a .NET 9 y .NET 10. No pueden usarse en proyectos que apunten a .NET 8, aunque `Sisk.HttpServer` lo soporte. ## Uso con Sisk Para usar Cadente como motor HTTP de tu aplicación Sisk, debes configurar el `HttpServer` para que utilice `CadenteHttpServerEngine` en lugar del motor predeterminado. El `CadenteHttpServerEngine` adapta el `HttpHost` de Cadente a la abstracción `HttpServerEngine` requerida por Sisk. ```csharp using Sisk.Core.Http; using Sisk.Cadente.CoreEngine; using var host = HttpServer.CreateBuilder() .UseEngine() .UseSsl(certificate: CertificateHelper.CreateTrustedDevelopmentCertificate("localhost")) .Build(); await host.StartAsync(); ``` Cuando un host de escucha está configurado con SSL, [HttpServer.ListeningPrefixes](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServer.ListeningPrefixes.md) informa sus direcciones con el esquema `https`. Si un cliente envía HTTP plano a un puerto configurado con SSL, Cadente responde con una redirección `301 Moved Permanently` a la dirección `https` del mismo host y ruta. ### Configuración avanzada Puedes personalizar la instancia subyacente de `HttpHost` pasando una acción de configuración al constructor de `CadenteHttpServerEngine`. Esto es útil para ajustar tiempos de espera u otras configuraciones de bajo nivel. ```csharp using var engine = new CadenteHttpServerEngine(host => { // Configurar tiempos de espera de lectura/escritura del cliente host.TimeoutManager.ClientReadTimeout = TimeSpan.FromSeconds(30); host.TimeoutManager.ClientWriteTimeout = TimeSpan.FromSeconds(30); }); ``` ## Uso independiente Aunque está diseñado principalmente para Sisk, Cadente puede usarse como un servidor HTTP independiente (similar a `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!"); } } ``` Cuando el cuerpo de la respuesta es pequeño y ya está en memoria, `WriteInlineContent` y `WriteInlineContentAsync` escriben los encabezados y el cuerpo en una única operación. La respuesta debe contener un encabezado `Content-Length`: ```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); ``` ## Validación de solicitudes Cadente valida el encuadre de la solicitud antes de que llegue al manejador. Desde la versión 1.7, realiza lo siguiente: - admite solicitudes en pipeline en conexiones keep-alive; - rechaza métodos de solicitud que no sean tokens HTTP válidos y destinos de solicitud que contengan bytes fuera del rango ASCII visible (`%x21-7E`); - rechaza solicitudes cuyo `Transfer-Encoding` sea distinto de un único valor `chunked`, y solicitudes con encabezados `Content-Length` conflictivos; - responde `431 Request Header Fields Too Large` y cierra la conexión cuando la línea de solicitud y los encabezados superan los 8 KB. Las solicitudes inválidas se responden con un error y la conexión se cierra, sin invocar al manejador. --- # Configuración de reservas de espacios de nombres en Windows Source: https://docs.sisk-framework.org/es/docs/registering-namespace.html > [!NOTE] > Esta configuración es opcional y solo se requiere cuando deseas que Sisk escuche en hosts diferentes a "localhost" en Windows usando el motor HttpListener. Sisk funciona con la interfaz de red HttpListener, que enlaza un host virtual al sistema para escuchar solicitudes. En Windows, este enlace es algo restrictivo, solo permite que localhost se vincule como un host válido. Al intentar escuchar en otro host, se lanza un error de acceso denegado en el servidor. Este tutorial explica cómo otorgar autorización para escuchar en cualquier host que desees en el sistema. ```bat {title="Namespace Setup.bat"} @echo off :: insert prefix here, without spaces or quotes SET PREFIX= SET DOMAIN=%ComputerName%\%USERNAME% netsh http add urlacl url=%PREFIX% user=%DOMAIN% pause ``` Donde en `PREFIX` está el prefijo ("Host de escucha->Puerto") que tu servidor escuchará. Debe estar formateado con el esquema URL, host, puerto y una barra al final, por ejemplo: ```bat {title="Namespace Setup.bat"} SET PREFIX=http://my-application.example.test/ ``` De modo que puedas escuchar en tu aplicación mediante: ```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(); } } ``` --- # Registros de cambios Source: https://docs.sisk-framework.org/es/docs/changelogs.html Cada cambio realizado en Sisk se registra en el registro de cambios. Puedes ver los registros de cambios de todas las versiones de Sisk [aquí](https://github.com/sisk-http/archive/tree/master/changelogs). ## Migración de 1.6 a 1.7 Sisk 1.7.0 incluye `Sisk.HttpServer`, `Sisk.Cadente`, `Sisk.Cadente.CoreEngine` y `Sisk.IniConfiguration` 1.7.0. `Sisk.Documenting`, `Sisk.Documenting.Html` y `Sisk.Monitoring` se entregan como 1.7.0-rc3, y `Sisk.ModelContextProtocol` como 1.7.0-rc1. Lee el [registro de cambios completo de 1.7.0](https://github.com/sisk-http/archive/blob/master/changelogs/core/v1.7/v1.7.0.md) para cada cambio. ### Cambios incompatibles - **`Route.UseRegex` ya no se puede establecer.** La propiedad está obsoleta y solo indica si la ruta es un `RegexRoute`. Crea un [RegexRoute](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RegexRoute.md) en su lugar. Los atributos de ruta con `UseRegex = true` y el atributo `[RegexRoute]` siguen funcionando. Ver [regex routes](https://docs.sisk-framework.org/es/docs/fundamentals/routing.md#regex-routes). ```cs // antes router.Map(new Route(RouteMethod.Get, @"^/items/(?\d+)$", action) { UseRegex = true }); // después router.Map(new RegexRoute(RouteMethod.Get, @"^/items/(?\d+)$", action)); ``` - **Los cuerpos de solicitud están limitados a 100 MB por defecto.** El valor predeterminado de [HttpServerConfiguration.MaximumContentLength](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.MaximumContentLength.md) cambió de `0` (ilimitado) a 100 MB. El límite también se aplica a los flujos de solicitud y a los mensajes WebSocket. Establécelo en `0` para restaurar el comportamiento anterior. Consulta [obtener el cuerpo de la solicitud](https://docs.sisk-framework.org/es/docs/fundamentals/requests.md#getting-the-request-body). ```cs using var app = HttpServer.CreateBuilder() .UseConfiguration(config => config.MaximumContentLength = 0) .Build(); ``` - **Las rutas estáticas se prueban antes que las rutas dinámicas.** Las rutas Regex, rutas de prefijo (incluidos los servidores de archivos) y rutas de coincidencia personalizada se prueban después de cada ruta estática, sin importar el orden de registro. Ver [precedencia de coincidencia](https://docs.sisk-framework.org/es/docs/fundamentals/routing.md#matching-precedence). - **Los nombres de ruta duplicados provocan una excepción al iniciar.** Dos o más rutas con el mismo `Name` generan una `ArgumentException` cuando el servidor se inicia, mientras que [Router.CheckForRouteCollisions](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.CheckForRouteCollisions.md) está habilitado (por defecto). - **Las rutas del servidor de archivos son rutas de prefijo.** `Router.MapFileSystem` y `HttpFileServer.CreateServingRoute` crean una [PrefixRoute](https://docs.sisk-framework.org/api/Sisk.Core.Routing.PrefixRoute.md) en lugar de una ruta regex, y ya no se ven afectadas por `ForceTrailingSlash`. - **`RouteMethod.Any` incluye `QUERY`.** Las rutas mapeadas con `MapAny` también responden a solicitudes `QUERY`. El valor numérico de `RouteMethod.Any` cambió, por lo que debes recompilar el código que lo almacena o lo compara. - **La preflight de CORS respeta `UseCors`.** La respuesta automática `OPTIONS` ya no envía encabezados CORS para rutas con `UseCors = false`, y las rutas mapeadas explícitamente para `OPTIONS` manejan la solicitud en su lugar. Ver [solicitudes de preflight](https://docs.sisk-framework.org/es/docs/features/cors.md#preflight-requests). - **Cadente rechaza solicitudes malformadas.** Los métodos y destinos de solicitud no válidos, los valores no compatibles de `Transfer-Encoding` y los encabezados `Content-Length` conflictivos son rechazados, y los bloques de encabezado mayores de 8 KB reciben `431 Request Header Fields Too Large`. Ver [validación de solicitudes de Cadente](https://docs.sisk-framework.org/es/docs/cadente.md#request-validation). ### Paquetes descontinuados `Sisk.BasicAuth`, `Sisk.SslProxy` y `Sisk.ServiceProvider` ya no se mantienen y están obsoletos en NuGet, sin paquete de reemplazo. Elimínalos de tus proyectos: - Reemplaza `Sisk.BasicAuth` con tu propio [manejador de solicitudes](https://docs.sisk-framework.org/es/docs/fundamentals/request-handlers.md) que valide el encabezado `Authorization`. - Reemplaza `Sisk.SslProxy` con el soporte SSL nativo del [motor Cadente](https://docs.sisk-framework.org/es/docs/cadente.md). - Reemplaza `Sisk.ServiceProvider` con la [configuración portable](https://docs.sisk-framework.org/es/docs/extensions/service-providers.md) incorporada en `Sisk.HttpServer` mediante `UsePortableConfiguration`. ### Nuevas características - [Coincidencia de rutas personalizada](https://docs.sisk-framework.org/es/docs/fundamentals/routing.md#custom-route-matching) a través de `Route.Match`, `RouteMatch`, `Route.IsStatic` y `Route.AllowRewrites`, y las nuevas [rutas de prefijo](https://docs.sisk-framework.org/es/docs/fundamentals/routing.md#prefix-routes). - Soporte para el [método QUERY] HTTP con `MapQuery`, `Route.Query`, `[RouteQuery]` y el encabezado `Accept-Query`. - [Valores atómicos de contexto](https://docs.sisk-framework.org/es/docs/fundamentals/requests.md#atomic-context-values) a través de `HttpContext.Interlocked`. - El término de registro de acceso `%{$key}` [término de registro de acceso](https://docs.sisk-framework.org/es/docs/features/logging.md#access-log-formatting), que imprime valores del contenedor de solicitud, y los [helpers de escritura segura de archivos](https://docs.sisk-framework.org/es/docs/features/logging.md#writing-files-safely) `LogStream.SafeWriteToFile` y `LogStream.SafeAppendToFile`. - En Cadente: solicitudes keep-alive en pipeline, `WriteInlineContent` y `WriteInlineContentAsync`, y una redirección a HTTPS para solicitudes HTTP simples recibidas en un puerto SSL. - En las extensiones: ejemplos de parámetros y `LlmsApiExporter.FooterText` en [Sisk.Documenting](https://docs.sisk-framework.org/es/docs/extensions/api-documentation.md), habilidades y la revisión de protocolo `2026-07-28` en [Sisk.ModelContextProtocol](https://docs.sisk-framework.org/es/docs/extensions/mcp.md), y telemetría de tráfico HTTP, latencia, rutas y excepciones en el panel de Sisk.Monitoring. ### Otros cambios de comportamiento - El motor `HttpListener` envía cada solicitud al pool de hilos, por lo que una solicitud lenta ya no retrasa la siguiente conexión aceptada. Su `ContextAbortedToken` devuelve `CancellationToken.None` en lugar de lanzar una excepción. - Las excepciones lanzadas por los manejadores del servidor al final de una solicitud, y al escribir registros, se escriben en el registro de errores en lugar de propagarse al motor HTTP. Ver [excepciones lanzadas por los manejadores](https://docs.sisk-framework.org/es/docs/advanced/http-server-handlers.md#exceptions-thrown-by-handlers). - Las respuestas CORS envían `Vary: Origin` cuando el origen permitido depende de la solicitud. - El término de registro de acceso `%ri` utiliza la dirección del cliente resuelta al inicio de la solicitud. - `Sisk.HttpServer` tiene como objetivo .NET 8, .NET 9 y .NET 10. `Sisk.Cadente` y `Sisk.Cadente.CoreEngine` tienen como objetivo .NET 9 y .NET 10 y publican sus símbolos de depuración como paquetes `.snupkg`. --- # Preguntas Frecuentes Source: https://docs.sisk-framework.org/es/docs/faq.html Preguntas frecuentes sobre Sisk. ## ¿Es Sisk de código abierto? Totalmente. Todo el código fuente utilizado por Sisk se publica y se actualiza con frecuencia en [GitHub](https://github.com/sisk-http). ## ¿Se aceptan contribuciones? Siempre y cuando sean compatibles con la [filosofía de Sisk](/), todas las contribuciones son muy bienvenidas. Las contribuciones no tienen que ser solo código. Puedes contribuir con documentación, pruebas, traducciones, donaciones y publicaciones, por ejemplo. ## ¿Está financiado Sisk? No. Ninguna organización o proyecto patrocina actualmente a Sisk. ## ¿Puedo usar Sisk en producción? Absolutamente. El proyecto lleva en desarrollo más de tres años y ha tenido una intensa prueba en aplicaciones comerciales que han estado en producción desde entonces. Sisk se utiliza en proyectos comerciales importantes como infraestructura principal. Una guía sobre cómo [implementar](https://docs.sisk-framework.org/es/docs/deploying.md) en diferentes sistemas y entornos se ha escrito y está disponible. ## ¿Tiene Sisk autenticación, monitoreo y servicios de base de datos? No. Sisk no tiene ninguno de estos. Es un framework para desarrollar aplicaciones web HTTP, pero es un framework minimalista que entrega lo necesario para que tu aplicación funcione. Puedes implementar todos los servicios que desees utilizando cualquier biblioteca de terceros que prefieras. Sisk fue diseñado para ser agnóstico, flexible y funcionar con cualquier cosa. ## ¿Por qué debería usar Sisk en lugar de ? No lo sé. Tú dime. Sisk se creó para llenar un escenario genérico para aplicaciones web HTTP en .NET. Proyectos establecidos, como ASP.NET, resuelven varios problemas, pero con diferentes sesgos. A diferencia de los frameworks más grandes, Sisk requiere que el usuario sepa lo que está haciendo y construyendo. Los conceptos básicos de desarrollo web y el protocolo HTTP son esenciales para trabajar con Sisk. Sisk se parece más a Express de Node.js que a ASP.NET Core. Es una abstracción de alto nivel que te permite crear aplicaciones con lógica HTTP que tú desees. ## ¿Qué necesito para aprender Sisk? Necesitas los conceptos básicos de: - Desarrollo web (HTTP, Restful, etc.) - .NET Eso es todo. Teniendo una noción de estos dos temas, puedes dedicar unas horas a desarrollar una aplicación avanzada con Sisk. ## ¿Puedo desarrollar aplicaciones comerciales con Sisk? Absolutamente. Sisk se creó bajo la licencia MIT, lo que significa que puedes usar Sisk en cualquier proyecto comercial, comercial o no comercial, sin necesidad de una licencia propietaria. Lo que pedimos es que en algún lugar de tu aplicación, tengas un aviso de los proyectos de código abierto utilizados en tu proyecto, y que Sisk esté allí. --- # Enrutamiento Source: https://docs.sisk-framework.org/es/docs/fundamentals/routing.html El [Router](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.md) es el primer paso para construir el servidor. Es responsable de albergar objetos [Route](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Route.md), que son puntos finales que asignan URLs y sus métodos a acciones ejecutadas por el servidor. Cada acción se encarga de recibir una solicitud y entregar una respuesta al cliente. Las rutas son pares de expresiones de ruta ("patrón de ruta") y el método HTTP al que pueden escuchar. Cuando se realiza una solicitud al servidor, éste intentará encontrar una ruta que coincida con la solicitud recibida, luego llamará a la acción de esa ruta y entregará la respuesta resultante al cliente. Existen múltiples formas de definir rutas en Sisk: pueden ser estáticas, dinámicas o auto‑exploradas, definidas por atributos, o directamente en el objeto Router. ```cs Router mainRouter = new Router(); // asigna la ruta GET / a la siguiente acción mainRouter.MapGet("/", request => { return new HttpResponse("Hello, world!"); }); ``` Para entender lo que una ruta es capaz de hacer, necesitamos entender lo que una solicitud es capaz de hacer. Un [HttpRequest](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.md) contendrá todo lo que necesitas. Sisk también incluye algunas características extra que aceleran el desarrollo en general. Para cada acción recibida por el servidor, se llamará a un delegado del tipo [RouteAction](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouteAction.md). Este delegado contiene un parámetro que lleva un [HttpRequest](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.md) con toda la información necesaria sobre la solicitud recibida por el servidor. El objeto resultante de este delegado debe ser un [HttpResponse](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpResponse.md) o un objeto que se mapee a él mediante [tipos de respuesta implícitos](https://docs.sisk-framework.org/es/docs/fundamentals/responses.md#implicit-response-types). ## Matching routes Cuando una solicitud es recibida por el servidor HTTP, Sisk busca una ruta que satisfaga la expresión del camino recibido por la solicitud. La expresión siempre se prueba entre la ruta y el camino de la solicitud, sin considerar la cadena de consulta. La primera ruta cuyo camino y método coincidan con la solicitud la maneja. Cuando no se encuentra ninguna ruta que coincida con esa solicitud, se devuelve al cliente la respuesta de [Router.NotFoundErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.NotFoundErrorHandler.md). Cuando el patrón de ruta coincide, pero el método HTTP no, se envía al cliente la respuesta de [Router.MethodNotAllowedErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.MethodNotAllowedErrorHandler.md). El orden en que se prueban las rutas se describe en [matching precedence](#matching-precedence). Sisk verifica la posibilidad de colisiones de rutas entre rutas estáticas. Cuando el servidor se inicia, Sisk buscará rutas estáticas cuyos patrones de ruta y métodos se solapen, y lanzará una excepción si se encuentra alguna. En la misma comprobación, Sisk lanza una `ArgumentException` si dos o más rutas comparten el mismo [Route.Name](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Route.Name.md). Ambas comprobaciones están controladas por [Router.CheckForRouteCollisions](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.CheckForRouteCollisions.md), que está habilitada por defecto. ### Matching precedence Cada ruta es **estática** o **dinámica**, según lo indique [Route.IsStatic](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Route.IsStatic.md): - **Rutas estáticas** siguen estrictamente su patrón de ruta, como `/users/`. Las rutas creadas con los métodos `Map*`, instancias de `Route` y atributos de ruta son estáticas. - **Rutas dinámicas** interpretan su camino a su manera y pueden coincidir con caminos que no pueden describirse mediante un patrón de ruta. [RegexRoute](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RegexRoute.md), [PrefixRoute](https://docs.sisk-framework.org/api/Sisk.Core.Routing.PrefixRoute.md) (usado por [file servers](https://docs.sisk-framework.org/es/docs/features/file-server.md)) y [rutas de coincidencia personalizadas](#custom-route-matching) son dinámicas. Cuando el servidor se inicia, el router reordena sus rutas una vez de modo que cada ruta estática se pruebe antes que cualquier ruta dinámica. Dentro de cada grupo, las rutas conservan su orden de registro. Esto significa que una ruta estática siempre tiene precedencia sobre una ruta dinámica, sin importar el orden en que fueron definidas: ```cs // dinámico: sirve cualquier ruta GET del directorio "wwwroot" mainRouter.MapFileSystem("/", "wwwroot"); // estático: maneja GET /api/status, aunque se definió después del servidor de archivos mainRouter.MapGet("/api/status", request => new HttpResponse("ok")); ``` En el ejemplo anterior, el servidor de archivos actúa como respaldo para cualquier solicitud GET que no sea manejada por una ruta estática. Los dos grupos también difieren en cómo se validan y transforman: | | Rutas estáticas | Rutas dinámicas | |---------------------|-----------------|-----------------| | Orden de coincidencia | Primero, en orden de registro | Después de todas las rutas estáticas, en orden de registro | | Comprobación de colisión de rutas | Sí | No | | Se aplica [Router.Prefix](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.Prefix.md) | Sí | No | > [!WARNING] > Las rutas dinámicas no se comprueban para colisiones, ya que Sisk no puede saber qué caminos coinciden. Cuando dos rutas dinámicas pueden coincidir con la misma solicitud, gana la que se definió primero. Por ejemplo, un servidor de archivos mapeado a `/` definido antes que un servidor de archivos mapeado a `/static` hace que el segundo sea inaccesible. > [!IMPORTANT] > En versiones anteriores a la 1.7, las rutas se probaban solo en su orden de registro, y las rutas regex podían tener precedencia sobre rutas estáticas definidas después de ellas. Si tu aplicación depende de que una ruta regex maneje un camino que también coincide con una ruta estática, mueve esa lógica a la ruta estática o estrecha el patrón de ruta estática. ### Creating routes using path patterns Para aplicaciones nuevas, prefiere los métodos `Map*`. Mantienen visible el método HTTP en el sitio de llamada y coinciden con la API actual del `Router`. Los métodos más antiguos `SetRoute` siguen existiendo como envoltorios de compatibilidad, pero los ejemplos nuevos deben usar `Map`, `MapGet`, `MapPost`, `MapPut`, `MapDelete`, `MapPatch`, `MapAny`, `MapOptions` o `MapHead`. ```cs // Los métodos Map* son la forma habitual de definir rutas específicas por método. 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(); // 200 ok vacío }); // Map también puede recibir una instancia de Route cuando necesitas opciones de ruta. mainRouter.Map(Route.Get("/image.png", (request) => { var imageStream = File.OpenRead("image.png"); return new HttpResponse() { // el contenido interno de StreamContent // el stream se libera después de enviar // la respuesta. Content = new StreamContent(imageStream) }; })); // múltiples parámetros mainRouter.MapGet("/hey//surname/", (request) => { string name = request.RouteParameters["name"].GetString(); string surname = request.RouteParameters["surname"].GetString(); return new HttpResponse($"Hello, {name} {surname}!"); }); ``` La propiedad [RouteParameters](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.RouteParameters.md) de HttpRequest contiene toda la información sobre las variables de ruta del request recibido. Cada camino recibido por el servidor se normaliza antes de ejecutar la prueba del patrón de ruta, siguiendo estas reglas: - Todos los segmentos vacíos se eliminan del camino, por ejemplo: `////foo//bar` se convierte en `/foo/bar`. - La coincidencia de rutas es **sensible a mayúsculas/minúsculas**, a menos que [Router.MatchRoutesIgnoreCase](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.MatchRoutesIgnoreCase.md) esté establecido en `true`. Las propiedades [Query](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Query.md) y [RouteParameters](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.RouteParameters.md) de [HttpRequest](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.md) devuelven un objeto [StringValueCollection](https://docs.sisk-framework.org/api/Sisk.Core.Entity.StringValueCollection.md), donde cada propiedad indexada devuelve un [StringValue](https://docs.sisk-framework.org/api/Sisk.Core.Entity.StringValue.md) no nulo, que puede usarse como una opción/monada para convertir su valor bruto en un objeto gestionado. El ejemplo a continuación lee el parámetro de ruta "id" y obtiene un `Guid` a partir de él. Si el parámetro no es un Guid válido, se lanza una excepción, y se devuelve un error 500 al cliente si el servidor no está manejando [Router.CallbackErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.CallbackErrorHandler.md). ```cs mainRouter.MapGet("/user/", (request) => { Guid id = request.RouteParameters["id"].GetGuid(); return new HttpResponse($"User id: {id}"); }); ``` > [!NOTE] > Los caminos ignoran su `/` final tanto en la solicitud como en la ruta, es decir, si intentas acceder a una ruta definida como `/index/page` también podrás acceder usando `/index/page/`. > > También puedes forzar que las URLs terminen con `/` habilitando [HttpServerConfiguration.ForceTrailingSlash](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ForceTrailingSlash.md). ### Creating routes using class instances También puedes definir rutas dinámicamente usando reflexión con el atributo [RouteAttribute](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouteAttribute.md). De esta forma, la instancia de una clase cuyas métodos implementen este atributo tendrá sus rutas definidas en el router objetivo. Para que un método sea definido como ruta, debe estar marcado con un [RouteAttribute](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouteAttribute.md), como el propio atributo o un [RouteGetAttribute](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouteGetAttribute.md). El método puede ser estático, de instancia, público o privado. Usa `MapInstance` cuando quieras mapear métodos de ruta de instancia y estáticos de un objeto. Usa `MapType` cuando quieras mapear solo métodos de ruta estáticos de un tipo. ```cs {title="Controller/MyController.cs"} public class MyController { // coincidirá con GET / [RouteGet] HttpResponse Index(HttpRequest request) { HttpResponse res = new HttpResponse(); res.Content = new StringContent("Index!"); return res; } // los métodos estáticos también funcionan [RouteGet("/hello")] static HttpResponse Hello(HttpRequest request) { HttpResponse res = new HttpResponse(); res.Content = new StringContent("Hello world!"); return res; } } ``` La línea siguiente definirá tanto los métodos `Index` como `Hello` de `MyController` como rutas, ya que ambos están marcados como rutas, y se ha proporcionado una instancia de la clase, no su tipo. Si se hubiera proporcionado su tipo en lugar de una instancia, solo se definirían los métodos estáticos. ```cs var myController = new MyController(); mainRouter.MapInstance(myController); ``` Para mapear solo los métodos de ruta estáticos de un tipo, usa: ```cs mainRouter.MapType(); ``` Desde la versión 0.16 de Sisk, es posible habilitar AutoScan, que buscará clases definidas por el usuario que implementen `RouterModule` y las asociará automáticamente con el router. Esto no es compatible con compilación AOT. ```cs mainRouter.AutoScanModules(); ``` La instrucción anterior buscará todos los tipos que implementen `ApiController` pero **no el propio tipo**. Los dos parámetros opcionales indican cómo el método buscará esos tipos. El primer argumento implica el ensamblado donde se buscarán los tipos y el segundo indica la forma en que los tipos serán definidos. ## Regex routes En lugar de usar los métodos de coincidencia de ruta HTTP por defecto, puedes definir una ruta cuyo camino se interprete como una expresión regular con la clase [RegexRoute](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RegexRoute.md). Las rutas regex son [rutas dinámicas](#matching-precedence). ```cs mainRouter.Map(new RegexRoute(RouteMethod.Get, @"\/[a-z]+\/", request => { return new HttpResponse("hello, world"); })); ``` También puedes capturar grupos del patrón regex en el contenido de [HttpRequest.RouteParameters](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.RouteParameters.md): ```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] > La expresión regular no se ancla automáticamente: un patrón como `/uploads` también coincide con `/files/uploads/a`. Usa `^` y `$` para coincidir con todo el camino. > [!IMPORTANT] > Desde Sisk 1.7, la propiedad `Route.UseRegex` ya no puede establecerse. Está obsoleta y solo indica si la ruta es una [RegexRoute](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RegexRoute.md). La propiedad `UseRegex` de los atributos de ruta, como `[Route(RouteMethod.Get, "...", UseRegex = true)]`, y el atributo `[RegexRoute]` siguen funcionando. Para migrar código que creaba rutas regex con `Route.UseRegex`, reemplaza la ruta por una `RegexRoute`: ```cs // antes de 1.7 mainRouter.Map(new Route(RouteMethod.Get, @"^/items/(?\d+)$", action) { UseRegex = true }); // 1.7 y posteriores mainRouter.Map(new RegexRoute(RouteMethod.Get, @"^/items/(?\d+)$", action)); ``` ## Prefix routes Una [PrefixRoute](https://docs.sisk-framework.org/api/Sisk.Core.Routing.PrefixRoute.md) coincide con su camino y con cualquier camino anidado bajo él, comparando segmentos de camino completos. Es la ruta usada por [Router.MapFileSystem](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.MapFileSystem.md). Las rutas prefijo son [rutas dinámicas](#matching-precedence). ```cs // coincide con /docs, /docs/ y /docs/any/nested/path, pero no con /docsfoo mainRouter.Map(new PrefixRoute(RouteMethod.Get, "/docs", null, request => { return new HttpResponse($"Requested {request.Path}"); }, null)); ``` ## Custom route matching Puedes crear tus propias reglas de coincidencia heredando de [Route](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Route.md) y sobrescribiendo [Route.Match](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Route.Match.md). El método recibe el camino de la solicitud y el router que está realizando la coincidencia, y devuelve un [RouteMatch](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouteMatch.md) con el resultado y, opcionalmente, los parámetros de ruta capturados. El router valida el método HTTP solo después de que `Match` tenga éxito, por lo que `Match` solo debe probar el camino. Esto mantiene funcionando las respuestas de [405 Method Not Allowed](#method-not-allowed-405-callback-handler) y `OPTIONS` para rutas personalizadas. ```cs {title="Routing/ExtensionRoute.cs"} // coincide con cualquier camino que termine con la extensión dada, como /reports/2024.csv public sealed class ExtensionRoute : Route { public ExtensionRoute(string extension, RouteAction action) : base(RouteMethod.Get, extension, action) { } // esta ruta no sigue un patrón de ruta public override bool IsStatic => false; // el camino no es un patrón de ruta, por lo que no puede redirigirse a su forma con barra final 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"); })); ``` Al sobrescribir `Match`, ten en cuenta: - Sobrescribe [Route.IsStatic](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Route.IsStatic.md) para devolver `false` siempre que la ruta no siga estrictamente un patrón de ruta. De lo contrario, el router trata su `Path` como un patrón de ruta al comprobar colisiones y aplicar [Router.Prefix](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.Prefix.md), lo que puede producir colisiones falsas o caminos inválidos. - Sobrescribe [Route.AllowRewrites](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Route.AllowRewrites.md) para devolver `false` cuando el camino de la ruta no pueda redirigirse a su forma con barra final mediante [HttpServerConfiguration.ForceTrailingSlash](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ForceTrailingSlash.md). - `Match` se llama para cada solicitud que llega a la ruta, posiblemente desde varios hilos simultáneamente. Evita asignaciones cuando la ruta no coincide y mantén cualquier estado en caché seguro para hilos. Devuelve [RouteMatch.NotMatched](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouteMatch.NotMatched.md) para coincidencias fallidas. - Respeta [Router.MatchRoutesIgnoreCase](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.MatchRoutesIgnoreCase.md) a través del parámetro `router`. ## Prefixing routes Puedes anteponer todas las rutas en una clase o módulo con el atributo [RoutePrefix](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RoutePrefixAttribute.md) y establecer el prefijo como una cadena. Mira el ejemplo siguiente usando la arquitectura BREAD (Browse, Read, Edit, Add and Delete): ```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() { ... } } ``` En el ejemplo anterior, el parámetro HttpResponse se omite a favor de usarlo a través del contexto global [HttpContext.Current](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpContext.Current.md). Lee más en la sección que sigue. ## Routes without request parameter Las rutas pueden definirse sin el parámetro [HttpRequest](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.md) y aún así es posible obtener la solicitud y sus componentes en el contexto de la solicitud. Consideremos una abstracción `ControllerBase` que sirve como base para todos los controladores de una API, y que dicha abstracción proporciona la propiedad `Request` para obtener el [HttpRequest](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.md) actual. ```cs {title="Controller/ControllerBase.cs"} public abstract class ControllerBase { // obtiene la solicitud del hilo actual public HttpRequest Request { get => HttpContext.Current.Request; } // la línea siguiente, cuando se llama, obtiene la base de datos de la sesión HTTP actual, // o crea una nueva si no existe public DbContext Database { get => HttpContext.Current.RequestBag.GetOrAdd(); } } ``` Y para que todos sus descendientes puedan usar la sintaxis de ruta sin el parámetro de solicitud: ```cs {title="Controller/UsersController.cs"} [RoutePrefix("/api/users")] public class UsersController : ControllerBase { [RoutePost] public async Task Create() { // lee los datos JSON de la solicitud actual UserCreationDto? user = await Request.GetJsonContentAsync(); ... Database.Users.Add(user); return new HttpResponse(201); } } ``` Más detalles sobre el contexto actual y la inyección de dependencias pueden encontrarse en el tutorial de [dependency injection](https://docs.sisk-framework.org/es/docs/features/instancing.md). ## Any method routes Puedes definir una ruta que se coincida solo por su camino y omitir el método HTTP. Esto puede ser útil para validar el método dentro del callback de la ruta. ```cs // coincidirá con / en cualquier método HTTP mainRouter.MapAny("/", callbackFunction); ``` [RouteMethod.Any](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouteMethod.md) incluye `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `QUERY`, `HEAD` y `OPTIONS`. ## QUERY method routes Sisk soporta el método HTTP `QUERY`, un método seguro e idempotente que lleva la consulta en el cuerpo de la solicitud. Usa [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) o el atributo [RouteQuery](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouteQueryAttribute.md) para definir estas rutas: ```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}"); } } ``` El encabezado `Accept-Query`, que anuncia los tipos de medio aceptados en solicitudes `QUERY`, está disponible a través de [HttpHeaderCollection.AcceptQuery](https://docs.sisk-framework.org/api/Sisk.Core.Entity.HttpHeaderCollection.AcceptQuery.md) y [HttpKnownHeaderNames.AcceptQuery](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpKnownHeaderNames.AcceptQuery.md). ## Any path routes Las rutas de cualquier camino prueban cualquier camino recibido por el servidor HTTP, sujeto al método de la ruta que se está probando. Si el método de la ruta es RouteMethod.Any y la ruta usa [Route.AnyPath](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Route.AnyPath.md) en su expresión de camino, esta ruta escuchará todas las solicitudes del servidor HTTP, y no se podrán definir otras rutas. ```cs // la siguiente ruta coincidirá con todas las solicitudes POST mainRouter.Map(RouteMethod.Post, Route.AnyPath, callbackFunction); ``` ## Ignore case route matching Por defecto, la interpretación de rutas con solicitudes es sensible a mayúsculas/minúsculas. Para hacer que ignore mayúsculas, habilita esta opción: ```cs mainRouter.MatchRoutesIgnoreCase = true; ``` Esto también habilitará la opción `RegexOptions.IgnoreCase` para rutas regex y la comparación sin distinción de mayúsculas para rutas prefijo. ## Not Found (404) callback handler Puedes crear un callback personalizado para cuando una solicitud no coincida con ninguna ruta conocida. ```cs mainRouter.NotFoundErrorHandler = () => { return new HttpResponse(404) { // Desde v0.14 Content = new HtmlContent("

Not found

") // versiones anteriores Content = new StringContent("

Not found

", Encoding.UTF8, "text/html") }; }; ``` ## Method not allowed (405) callback handler También puedes crear un callback personalizado para cuando una solicitud coincida con su camino, pero no con el método. ```cs mainRouter.MethodNotAllowedErrorHandler = (context) => { return new HttpResponse(405) { Content = new StringContent($"Method not allowed for this route.") }; }; ``` ## Error Handling Las excepciones pueden lanzarse dentro del ciclo de vida de una solicitud, que abarca desde el manejador de solicitud previo a la ejecución, pasando por la acción del router, hasta los manejadores de solicitud posteriores y los manejadores de valores. Estas excepciones son gestionadas por el mecanismo: - Si [HttpServerConfiguration.ThrowExceptions](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ThrowExceptions.md) es `true`, las excepciones se lanzarán normalmente y no serán capturadas por Sisk, y el servidor HTTP puede interrumpirse si la excepción no se captura. - Si [HttpServerConfiguration.ThrowExceptions](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ThrowExceptions.md) es `false`, las excepciones serán capturadas y manejadas por Sisk. Después de eso, si `Router.CallbackErrorHandler` está definido, se llamará con la excepción capturada y el contexto de la solicitud, y **no** se reenviará a la salida de error estándar. Si `Router.CallbackErrorHandler` no está definido, la excepción se reenviará a la salida de error estándar, y el cliente recibirá una respuesta HTTP 500. Si la salida de error estándar no está definida, el error se ignorará silenciosamente. Nota: dentro de `Router.CallbackErrorHandler`, puedes establecer el modo de registro para errores, registro de acceso, ambos o ninguno, y alterar el comportamiento predeterminado de escritura de logs: ```csharp router.CallbackErrorHandler = (ex, ctx) => { ctx.LogMode = LogOutput.Both; // sobrescribe el modo de registro para registrar el error tanto en el log de acceso como en el de errores } ``` ## Internal error handler Los callbacks de ruta pueden lanzar errores durante la ejecución del servidor. Si no se manejan correctamente, el funcionamiento general del servidor HTTP puede terminar. El router tiene un callback para cuando un callback de ruta falla y evita la interrupción del servicio. Este método solo es accesible cuando [ThrowExceptions](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ThrowExceptions.md) está configurado en false. ```cs mainRouter.CallbackErrorHandler = (ex, context) => { return new HttpResponse(500) { Content = new StringContent($"Error: {ex.Message}") }; }; ``` --- # Manejo de solicitudes Source: https://docs.sisk-framework.org/es/docs/fundamentals/request-handlers.html Los manejadores de solicitudes, también conocidos como "middlewares", son funciones que se ejecutan antes o después de que una solicitud sea procesada por el router. Pueden definirse por ruta o por router. Existen dos tipos de manejadores de solicitudes: - **BeforeResponse**: define que el manejador de solicitudes se ejecutará antes de llamar a la acción del router. - **AfterResponse**: define que el manejador de solicitudes se ejecutará después de llamar a la acción del router. Enviar una respuesta HTTP en este contexto sobrescribirá la respuesta de la acción del router. Ambos manejadores de solicitudes pueden sobrescribir la respuesta real de la función de devolución de llamada del router. Por cierto, los manejadores de solicitudes pueden ser útiles para validar una solicitud, como autenticación, contenido u otra información, como almacenar datos, registros u otros pasos que pueden realizarse antes o después de una respuesta. ![](https://docs.sisk-framework.org/assets/img/requesthandlers1.png) De esta manera, un manejador de solicitudes puede interrumpir toda esta ejecución y devolver una respuesta antes de que finalice el ciclo, descartando todo lo demás en el proceso. Ejemplo: supongamos que un manejador de solicitudes de autenticación de usuario no lo autentica. Impedirá que el ciclo de vida de la solicitud continúe y se quedará colgado. Si esto ocurre en el manejador de solicitudes en la posición dos, el tercero y los siguientes no se evaluarán. ![](https://docs.sisk-framework.org/assets/img/requesthandlers2.png) ## Creando un manejador de solicitudes Para crear un manejador de solicitudes, podemos crear una clase que herede de la interfaz [IRequestHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.IRequestHandler.md), con el siguiente formato: ```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) { // Devolver null indica que el ciclo de la solicitud puede continuar return null; } else { // Devolver un objeto HttpResponse indica que esta respuesta sobrescribirá las respuestas adyacentes. return new HttpResponse(System.Net.HttpStatusCode.Unauthorized); } } } ``` En el ejemplo anterior, indicamos que si el encabezado `Authorization` está presente en la solicitud, debe continuar y se debe llamar al siguiente manejador de solicitudes o a la devolución de llamada del router, lo que sea que venga después. Si un manejador de solicitudes se ejecuta después de la respuesta mediante su propiedad [ExecutionMode](https://docs.sisk-framework.org/api/Sisk.Core.Routing.IRequestHandler.ExecutionMode.md) y devuelve un valor no nulo, sobrescribirá la respuesta del router. Cada vez que un manejador de solicitudes devuelve `null`, indica que la solicitud debe continuar y se debe llamar al siguiente objeto o que el ciclo debe terminar con la respuesta del router. > [!WARNING] > Una instancia de un manejador de solicitudes se crea una sola vez y se comparte entre todas las solicitudes que maneja, incluidas las solicitudes concurrentes. **Nunca almacenes el estado individual de una solicitud en la propia clase del manejador** (campos o propiedades como el usuario actual, un token analizado o un temporizador), porque ese valor se filtra a otras solicitudes. Almacena los valores por solicitud en el [RequestBag](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpContext.RequestBag.md) del `HttpContext` en su lugar: > > ```cs > public HttpResponse? Execute(HttpRequest request, HttpContext context) > { > // incorrecto: this.currentUser = FindUser(request); > context.RequestBag.Set(FindUser(request)); > return null; > } > ``` Si heredas de la clase incorporada [RequestHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RequestHandler.md), puedes devolver `Next()` para hacer explícita esa intención: ```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); } } ``` Para manejadores que necesiten I/O, hereda de [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(); } } ``` Pequeños manejadores en línea también pueden crearse con `RequestHandler.Create` o `AsyncRequestHandler.Create`: ```cs var requireJson = RequestHandler.Create((request, context) => { if (request.Headers.ContentType?.Contains("application/json") == true) return null; return new HttpResponse(System.Net.HttpStatusCode.UnsupportedMediaType); }); ``` ## Asociando un manejador de solicitudes a una única ruta Puedes definir uno o más manejadores de solicitudes para una ruta. ```cs {title="Router.cs"} mainRouter.Map(RouteMethod.Get, "/", IndexPage, new IRequestHandler[] { new AuthenticateUserRequestHandler(), // before request handler new ValidateJsonContentRequestHandler(), // before request handler // -- method IndexPage will be executed here new WriteToLogRequestHandler() // after request handler }); ``` O creando un objeto [Route](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Route.md): ```cs {title="Router.cs"} Route indexRoute = Route.Get("/", IndexPage); indexRoute.RequestHandlers = new IRequestHandler[] { new AuthenticateUserRequestHandler() }; mainRouter.Map(indexRoute); ``` ## Asociando un manejador de solicitudes a un router Puedes definir un manejador de solicitudes global que se ejecutará en todas las rutas de un router. ```cs {title="Router.cs"} mainRouter.GlobalRequestHandlers = new IRequestHandler[] { new AuthenticateUserRequestHandler() }; ``` ## Asociando un manejador de solicitudes con un atributo Puedes definir un manejador de solicitudes en un atributo de método junto con un atributo de ruta. ```cs {title="Controller/MyController.cs"} public class MyController { [RouteGet("/")] [RequestHandler] static HttpResponse Index(HttpRequest request) { return new HttpResponse() { Content = new StringContent("Hello world!") }; } } ``` Ten en cuenta que es necesario pasar el tipo de manejador de solicitudes deseado y no una instancia de objeto. De esa forma, el manejador de solicitudes será instanciado por el analizador del router. Puedes pasar argumentos en el constructor de la clase mediante la propiedad [ConstructorArguments](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RequestHandlerAttribute.ConstructorArguments.md). Ejemplo: ```cs {title="Controller/MyController.cs"} [RequestHandler("arg1", 123, ...)] public HttpResponse Index(HttpRequest request) { return res = new HttpResponse() { Content = new StringContent("Hello world!") }; } ``` También puedes crear tu propio atributo que implemente RequestHandler: ```cs {title="Middleware/Attributes/AuthenticateAttribute.cs"} public class AuthenticateAttribute : RequestHandlerAttribute { public AuthenticateAttribute() : base(typeof(AuthenticateUserRequestHandler), ConstructorArguments = new object?[] { "arg1", 123, ... }) { ; } } ``` Y usarlo así: ```cs {title="Controller/MyController.cs"} [Authenticate] static HttpResponse Index(HttpRequest request) { return res = new HttpResponse() { Content = new StringContent("Hello world!") }; } ``` ## Omitiendo un manejador de solicitudes global Después de definir un manejador de solicitudes global en una ruta, puedes omitir este manejador de solicitudes en rutas específicas. ```cs {title="Router.cs"} var myRequestHandler = new AuthenticateUserRequestHandler(); mainRouter.GlobalRequestHandlers = new IRequestHandler[] { myRequestHandler }; Route publicRoute = Route.Get("/", IndexPage); publicRoute.Name = "My route"; publicRoute.BypassGlobalRequestHandlers = new IRequestHandler[] { myRequestHandler, // ok: the same instance of what is in the global request handlers new AuthenticateUserRequestHandler() // wrong: will not skip the global request handler }; mainRouter.Map(publicRoute); ``` > [!NOTE] > Si estás omitiendo un manejador de solicitudes, debes usar la misma referencia de la instancia que creaste antes para saltarlo. Crear otra instancia de manejador de solicitudes no omitirá el manejador global, ya que su referencia cambiará. Recuerda usar la misma referencia del manejador de solicitudes tanto en GlobalRequestHandlers como en BypassGlobalRequestHandlers. --- # Solicitudes Source: https://docs.sisk-framework.org/es/docs/fundamentals/requests.html Las solicitudes son estructuras que representan un mensaje de petición HTTP. El objeto [HttpRequest](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.md) 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: ```cs static HttpResponse Index(HttpRequest request) { HttpMethod requestMethod = request.Method; ... } ``` Esta propiedad devuelve el método de la solicitud representado por un objeto [HttpMethod](https://learn.microsoft.com/pt-br/dotnet/api/system.net.http.httpmethod) . > [!NOTE] > A diferencia de los métodos de ruta, esta propiedad no sirve al elemento [RouteMethod.Any](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouteMethod.md). 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/login?email=foo@bar.com ``` | Nombre del componente | Descripción | Valor del componente | | --- | --- | --- | | [Path](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Path.md) | Obtiene la ruta de la solicitud. | `/user/login` | | [FullPath](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.FullPath.md) | Obtiene la ruta de la solicitud y la cadena de consulta. | `/user/login?email=foo@bar.com` | | [FullUrl](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.FullUrl.md) | Obtiene la cadena completa de la URL de la solicitud. | `http://localhost:5000/user/login?email=foo@bar.com` | | [Host](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Host.md) | Obtiene el host de la solicitud. | `localhost` | | [Authority](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Authority.md) | Obtiene el host y el puerto de la solicitud. | `localhost:5000` | | [QueryString](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.QueryString.md) | Obtiene la cadena de consulta de la solicitud. | `?email=foo@bar.com` | | [Query](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Query.md) | Obtiene la consulta de la solicitud en una colección de valores con nombre. | `{StringValueCollection object}` | | [IsSecure](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.IsSecure.md) | Determina si la solicitud está usando SSL (true) o no (false). | `false` | También puede optar por usar la propiedad [HttpRequest.Uri](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Uri.md), 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étodo | Uso | | --- | --- | | [RequestId](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.RequestId.md) | Un identificador único para la solicitud. Habilite [IncludeRequestIdHeader](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.IncludeRequestIdHeader.md) para devolverlo como `X-Request-Id`. | | [RequestedAt](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.RequestedAt.md) | El momento en que Sisk creó el objeto de solicitud. | | [RemoteAddress](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.RemoteAddress.md) | La dirección del cliente resuelta a partir de la conexión, o de su [ForwardingResolver](https://docs.sisk-framework.org/es/docs/advanced/forwarding-resolvers.md). | | [Culture](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Culture.md) | La mejor cultura resuelta a partir de `Accept-Language`, retrocediendo a la cultura actual. | | [DisconnectToken](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.DisconnectToken.md) | Un token de cancelación que se activa cuando el cliente se desconecta, cuando es soportado por el motor HTTP configurado. | | [Bag](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Bag.md) | Un almacén tipado de clave/valor compartido entre los manejadores de solicitud y la acción de ruta. | | [GetRawHttpRequest](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.GetRawHttpRequest.md) | Una 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: ```cs // 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 bodyMemory = await request.GetBodyContentsAsync(); ``` También es posible determinar si hay un cuerpo en la solicitud y si está cargado con las propiedades [HasContents](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.HasContents.md), que determina si la solicitud tiene contenido y [IsContentAvailable](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.IsContentAvailable.md) , 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](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.RequestEncoding.md) 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](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Body.md) como a [HttpRequest.RawBody](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Body.md). Estas propiedades copian todo el flujo de entrada a un búfer local del mismo tamaño que [HttpRequest.ContentLength](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.ContentLength.md). El contenido de la solicitud está limitado por [HttpServerConfiguration.MaximumContentLength](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.MaximumContentLength.md), 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](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) o el flujo devuelto por [GetRequestStream](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.GetRequestStream.md) lanza una [InsufficientMemoryException](https://learn.microsoft.com/en-us/dotnet/api/system.insufficientmemoryexception), lo que resulta en un `500 Internal Server Error` a menos que su código o [Router.CallbackErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.CallbackErrorHandler.md) lo maneje. - El mismo límite es el tamaño máximo de un mensaje [WebSocket](https://docs.sisk-framework.org/es/docs/features/websockets.md) recibido. Establezca la propiedad en `0` para eliminar el límite: ```cs 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](https://learn.microsoft.com/en-us/dotnet/api/system.array.maxlength) (aproximadamente 2 GB) y lanzan una excepción cuando intenta acceder a ellos. Puede seguir manejando contenido mayor mediante transmisión. > [!NOTE] > 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"](https://httpwg.org/spec/rfc9110.html). ## Lectura de solicitudes JSON Para APIs JSON, prefiera los ayudantes JSON incorporados en lugar de leer `Body` y deserializar manualmente. Utilizan [System.Text.Json](https://learn.microsoft.com/en-us/dotnet/api/system.text.json) y por defecto usan [HttpRequest.DefaultJsonSerializerOptions](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.DefaultJsonSerializerOptions.md). ```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); }); ``` 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: ```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); }); ``` Puede pasar [JsonSerializerOptions](https://learn.microsoft.com/en-us/dotnet/api/system.text.json.jsonserializeroptions) personalizados para un punto final específico: ```cs var options = new JsonSerializerOptions(JsonSerializerDefaults.Web) { PropertyNameCaseInsensitive = true }; UserDto? user = request.GetJsonContent(options); ``` Para aplicaciones Native AOT o sensibles al recorte, use la sobrecarga `JsonTypeInfo` generada por un `JsonSerializerContext`: ```cs [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](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpContext.md) 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. ```cs HttpContext context = HttpContext.GetCurrentContext(); ``` ### Modo de registro La propiedad [HttpContext.LogMode](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpContext.LogMode.md) 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. ```cs // Deshabilitar el registro para esta solicitud context.LogMode = LogOutputMode.None; ``` ### Bolsa de solicitud El objeto [RequestBag](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpContext.RequestBag.md) 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. > [!TIP] > Esta propiedad también es accesible mediante la propiedad [HttpRequest.Bag](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Bag.md). ```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); } } } ``` El manejador de solicitud anterior definirá `AuthenticatedUser` en la bolsa de solicitud, y podrá ser consumido más tarde en el callback final: ```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}!") }; } } ``` 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. ```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(); ... } ``` ### 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](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpContext.Interlocked.md). Devuelve un objeto [HttpContextInterlocked](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpContext.HttpContextInterlocked.md) con operaciones atómicas sobre valores nombrados de la bolsa de solicitud: | Método | Descripció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`. ```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] > 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](https://docs.sisk-framework.org/api/Sisk.Core.Entity.StringKeyStoreCollection.md) con el ejemplo a continuación: ```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)) { ... } } ``` La versión async es útil cuando el cuerpo de la solicitud puede ser grande o cuando desea soporte de cancelación: ```cs 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. ```cs {title="Controller/Auth.cs"} [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](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.GetMultipartFormContentAsync.md) cuando la ruta sea asíncrona: ```cs var multipartFormDataObjects = await request.GetMultipartFormContentAsync(request.DisconnectToken); ``` Puede leer más sobre los [objetos de formulario multipart](https://docs.sisk-framework.org/api/Sisk.Core.Entity.MultipartObject.md) 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](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.DisconnectToken.md). 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. ```csharp 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](https://docs.sisk-framework.org/es/docs/cadente.md) 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](https://developer.mozilla.org/en-US/docs/Web/API/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](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.GetEventSource.md) 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](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequestEventSource.Close.md), 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](https://developer.mozilla.org/en-US/docs/Web/API/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. ```html {title="sse-example.html"} Fruits:
    ``` Y enviar progresivamente los mensajes al cliente: ```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(); } } ``` 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](https://docs.sisk-framework.org/es/docs/advanced/forwarding-resolvers.md). ## 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](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.NormalizeHeadersEncodings.md). --- # Respuestas Source: https://docs.sisk-framework.org/es/docs/fundamentals/responses.html Las respuestas representan objetos que son respuestas HTTP a solicitudes HTTP. Son enviadas por el servidor al cliente como una indicación de la solicitud de un recurso, página, documento, archivo u otro objeto. Una respuesta HTTP se compone de estado, encabezados y contenido. En este documento, le enseñaremos cómo estructurar respuestas HTTP con Sisk. ## Configuración de un estado HTTP La lista de estados HTTP es la misma desde HTTP/1.0, y Sisk soporta todos ellos. ```cs HttpResponse res = new HttpResponse(); res.Status = System.Net.HttpStatusCode.Accepted; // 202 ``` O con sintaxis Fluent: ```cs new HttpResponse() .WithStatus(200) // or .WithStatus(HttpStatusCode.Ok) // or .WithStatus(HttpStatusInformation.Ok); ``` Puede ver la lista completa de HttpStatusCode disponibles [aquí](https://learn.microsoft.com/pt-br/dotnet/api/system.net.httpstatuscode). También puede proporcionar su propio código de estado usando la estructura [HttpStatusInformation](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpStatusInformation.md). ## Cuerpo y tipo de contenido Sisk soporta objetos de contenido nativos de .NET para enviar el cuerpo en respuestas. Puede usar la clase [StringContent](https://learn.microsoft.com/pt-br/dotnet/api/system.net.http.stringcontent) para enviar una respuesta JSON, por ejemplo: ```cs HttpResponse res = new HttpResponse(); res.Content = new StringContent(myJson, Encoding.UTF8, "application/json"); ``` El servidor siempre intentará calcular el `Content-Length` a partir de lo que haya definido en el contenido si no lo ha definido explícitamente en un encabezado. Si el servidor no puede obtener implícitamente el encabezado Content-Length del contenido de la respuesta, la respuesta se enviará con codificación Chunked. También puede transmitir la respuesta enviando un [StreamContent](https://learn.microsoft.com/pt-br/dotnet/api/system.net.http.streamcontent) o usando el método [GetResponseStream](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.GetResponseStream.md). ## Encabezados de respuesta Puede agregar, editar o eliminar encabezados que envía en la respuesta. El ejemplo a continuación muestra cómo enviar una respuesta de redirección al cliente. ```cs HttpResponse res = new HttpResponse(); res.Status = HttpStatusCode.Moved; res.Headers.Add(HttpKnownHeaderNames.Location, "/login"); ``` O con sintaxis Fluent: ```cs new HttpResponse(301) .WithHeader("Location", "/login"); ``` Cuando usa el método [Add](https://docs.sisk-framework.org/api/Sisk.Core.Entity.HttpHeaderCollection.Add.md) de `HttpHeaderCollection`, está añadiendo un encabezado a la solicitud sin alterar los que ya fueron enviados. El método [Set](https://docs.sisk-framework.org/api/Sisk.Core.Entity.HttpHeaderCollection.Set.md) reemplaza los encabezados con el mismo nombre por el valor indicado. El indexador de `HttpHeaderCollection` llama internamente al método `Set` para reemplazar los encabezados. También puede obtener valores de encabezados usando el método [GetHeaderValue](https://docs.sisk-framework.org/api/Sisk.Core.Entity.HttpHeaderCollection.GetHeaderValue.md). Este método ayuda a obtener valores tanto de los encabezados de respuesta como de los encabezados de contenido (si se ha establecido contenido). ```cs // Devuelve el valor del encabezado "Content-Type", verificando tanto response.Headers como response.Content.Headers string? contentType = response.GetHeaderValue("Content-Type"); ``` ## Envío de cookies Sisk tiene métodos que facilitan la definición de cookies en el cliente. Las cookies establecidas con este método ya están codificadas en URL y cumplen con el estándar RFC-6265. ```cs HttpResponse res = new HttpResponse(); res.SetCookie("cookie-name", "cookie-value"); ``` O con sintaxis Fluent: ```cs new HttpResponse(301) .WithCookie("cookie-name", "cookie-value", expiresAt: DateTime.Now.Add(TimeSpan.FromDays(7))); ``` Existen otras [versiones más completas](https://docs.sisk-framework.org/api/Sisk.Core.Helpers.CookieHelper.SetCookie.md) del mismo método. ## Respuestas fragmentadas Puede establecer la codificación de transferencia a chunked para enviar respuestas grandes. ```cs HttpResponse res = new HttpResponse(); res.SendChunked = true; ``` Al usar codificación chunked, el encabezado Content-Length se omite automáticamente. ## Flujo de respuesta Los flujos de respuesta son una forma gestionada que le permite enviar respuestas de manera segmentada. Es una operación de nivel más bajo que usar objetos `HttpResponse`, ya que requiere que envíe los encabezados y el contenido manualmente, y luego cierre la conexión. Este ejemplo abre un flujo de solo lectura para el archivo, copia el flujo al flujo de salida de la respuesta y no carga todo el archivo en memoria. Esto puede ser útil para servir archivos medianos o grandes. ```cs // obtiene el flujo de salida de la respuesta using var fileStream = File.OpenRead("my-big-file.zip"); var responseStream = request.GetResponseStream(); // establece la codificación de la respuesta para usar chunked-encoding // también no debe enviar el encabezado content-length al usar // codificación chunked responseStream.SendChunked = true; responseStream.SetStatus(200); responseStream.SetHeader(HttpKnownHeaderNames.ContentType, contentType); // copia el flujo del archivo al flujo de salida de la respuesta fileStream.CopyTo(responseStream.ResponseStream); // cierra el flujo return responseStream.Close(); ``` ## Compresión GZip, Deflate y Brotli Puede enviar respuestas con contenido comprimido en Sisk comprimiendo contenidos HTTP. Primero, encapsule su objeto [HttpContent](https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpcontent) dentro de uno de los compresores a continuación para enviar la respuesta comprimida al cliente. ```cs router.MapGet("/hello.html", request => { string myHtml = "..."; return new HttpResponse () { Content = new GZipContent(new HtmlContent(myHtml)), // or Content = new BrotliContent(new HtmlContent(myHtml)), // or Content = new DeflateContent(new HtmlContent(myHtml)), }; }); ``` También puede usar estos contenidos comprimidos con flujos. ```cs router.MapGet("/archive.zip", request => { // no aplique "using" aquí. el HttpServer descartará su contenido // después de enviar la respuesta. var archive = File.OpenRead("/path/to/big-file.zip"); return new HttpResponse () { Content = new GZipContent(archive) } }); ``` Los encabezados `Content-Encoding` se establecen automáticamente al usar estos contenidos. ## Compresión automática Es posible comprimir automáticamente las respuestas HTTP con la propiedad [EnableAutomaticResponseCompression](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.EnableAutomaticResponseCompression.md). Esta propiedad encapsula automáticamente el contenido de la respuesta del router en un contenido comprimible que es aceptado por la solicitud, siempre que la respuesta no herede de un [CompressedContent](https://docs.sisk-framework.org/api/Sisk.Core.Http.CompressedContent.md). Solo se elige un contenido comprimible por solicitud, seleccionado según el encabezado `Accept-Encoding`, que sigue el orden: - [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) Si la solicitud indica que acepta cualquiera de estos métodos de compresión, la respuesta se comprimirá automáticamente. ## Tipos de respuesta implícitos Puede usar otros tipos de retorno además de `HttpResponse`, pero es necesario configurar el router para que sepa cómo manejar cada tipo de objeto. El concepto es siempre devolver un tipo de referencia y convertirlo en un objeto `HttpResponse` válido. Las rutas que devuelven `HttpResponse` no sufren ninguna conversión. Los tipos de valor (estructuras) no pueden usarse como tipo de retorno porque no son compatibles con el [RouterCallback](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouterCallback.md), por lo que deben envolver‑se en un `ValueResult` para poder ser usados en los manejadores. Considere el siguiente ejemplo de un módulo de router que no usa `HttpResponse` en el tipo de retorno: ```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; } } ``` Con eso, ahora es necesario definir en el router cómo se tratará cada tipo de objeto. Los objetos son siempre el primer argumento del manejador y el tipo de salida debe ser un `HttpResponse` válido. Además, los objetos de salida de una ruta nunca deben ser nulos. Para los tipos `ValueResult` no es necesario indicar que el objeto de entrada es un `ValueResult` y solo `T`, ya que `ValueResult` es un objeto reflejado de su componente original. La asociación de tipos no compara lo que se registró con el tipo del objeto devuelto por el callback del router. En su lugar, verifica si el tipo del resultado del router es asignable al tipo registrado. Registrar un manejador del tipo `Object` actuará como fallback para todos los tipos previamente no validados. El orden de inserción de los manejadores de valor también importa, por lo que registrar un manejador `Object` ignorará todos los demás manejadores específicos de tipo. Siempre registre primero los manejadores de valor específicos para asegurar el orden. ```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)); }); // registrar un manejador de valor de tipo object debe ser el último // manejador de valor que se usará como fallback r.RegisterValueHandler(fallback => { return new HttpResponse() { Status = HttpStatusCode.OK, Content = JsonContent.Create(fallback) }; }); ``` ## Acciones diferidas Cuando una solicitud llega al router, primero pasa por los [request handlers](https://docs.sisk-framework.org/es/docs/fundamentals/request-handlers.md), se procesa en la acción del router y luego por los manejadores de solicitud post‑ejecución. El resultado de la acción del router es lo que se pasa a los manejadores de valor, y el resultado del manejador de valor es lo que se envía al cliente como respuesta. Este ciclo de vida ocurre dentro de un contexto asíncrono. Este contexto asíncrono expone variables que el usuario puede agregar al [HttpContext Bag](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpContext.md) para compartir datos entre los manejadores y la acción del router. El valor devuelto por la acción del router se agrega a este contexto asíncrono y puede ser accedido por los manejadores de valor. Las acciones diferidas son acciones que siempre se ejecutarán al final del ciclo, después de entregar la respuesta al cliente, pero aún dentro del mismo contexto asíncrono. Estas acciones pueden usarse para ejecutar tareas de larga duración que no necesitan completarse para enviar una respuesta al cliente, como guardar logs, actualizar la base de datos, enviar correos electrónicos, etc. Las excepciones siguen siendo capturadas en las acciones diferidas y se manejarán de la misma forma que una excepción lanzada en cualquier punto del ciclo de vida de la solicitud. La diferencia es que el cliente ya habrá recibido una respuesta, por lo que la excepción se maneja mediante el manejo de errores predeterminado. Aplazar la ejecución de una acción usando el método [HttpContext.EnqueueDeferredAction](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpContext.EnqueueDeferredAction.md). El método recibe una función asíncrona que representa la acción a ejecutar y un tiempo de espera opcional para limitar el tiempo de ejecución de la acción. Si la acción no se completa dentro del límite de tiempo, será cancelada. ```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."); } // programa una acción de larga duración que se ejecutará después de enviar la respuesta al cliente, pero aún dentro del mismo contexto asíncrono de la solicitud 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...") }; } ``` ## Nota sobre objetos enumerables y matrices Los objetos de respuesta implícitos que implementan [IEnumerable](https://learn.microsoft.com/pt-br/dotnet/api/system.collections.ienumerable?view=net-8.0) se leen en memoria mediante el método `ToArray()` antes de ser convertidos a través de un manejador de valor definido. Para que esto ocurra, el objeto `IEnumerable` se convierte en una matriz de objetos, y el convertidor de respuesta siempre recibirá un `Object[]` en lugar del tipo original. Considere el siguiente escenario: ```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(); ``` En el ejemplo anterior, el convertidor `IEnumerable` **nunca será llamado**, porque el objeto de entrada siempre será un `Object[]` y no es convertible a `IEnumerable`. Sin embargo, el convertidor que recibe un `IEnumerable` sí recibirá su entrada, ya que su valor es compatible. Si necesita manejar realmente el tipo del objeto que será enumerado, deberá usar reflexión para obtener el tipo del elemento de la colección. Todos los objetos enumerables (listas, matrices y colecciones) son convertidos a una matriz de objetos por el convertidor de respuestas HTTP. Los valores que implementan [IAsyncEnumerable](https://learn.microsoft.com/pt-br/dotnet/api/system.collections.generic.iasyncenumerable-1?view=net-8.0) son manejados automáticamente por el servidor si la propiedad [ConvertIAsyncEnumerableIntoEnumerable](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ConvertIAsyncEnumerableIntoEnumerable.md) está habilitada, de forma similar a lo que ocurre con `IEnumerable`. Esta opción está habilitada por defecto en `HttpServerConfiguration`; una enumeración asíncrona se convierte en un enumerador bloqueante y luego en una matriz síncrona de objetos. Desactívela solo cuando proporcione su propio manejador de valor o una estrategia de respuesta en streaming para secuencias asíncronas. --- # Registro Source: https://docs.sisk-framework.org/es/docs/features/logging.html Puedes configurar Sisk para que escriba automáticamente registros de acceso y de errores. Es posible definir la rotación de logs, extensiones y frecuencia. La clase [LogStream](https://docs.sisk-framework.org/api/Sisk.Core.Http.LogStream.md) proporciona una forma asíncrona de escribir logs y mantenerlos en una cola de escritura awaitable. La clase `LogStream` implementa `IAsyncDisposable`, garantizando que todos los logs pendientes se escriban antes de que el stream se cierre. En este artículo te mostraremos cómo configurar el registro para tu aplicación. ## Registros de acceso basados en archivos Los logs a archivos abren el archivo, escriben la línea de texto y luego cierran el archivo por cada línea escrita. Este procedimiento se adoptó para mantener la capacidad de respuesta de escritura en los logs. ```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(); } } ``` El código anterior escribirá todas las solicitudes entrantes en el archivo `logs/access.log`. Ten en cuenta que el archivo se crea automáticamente si no existe, sin embargo la carpeta que lo contiene no. No es necesario crear el directorio `logs/` ya que la clase LogStream lo crea automáticamente. ## Registro basado en flujos Puedes escribir archivos de log en instancias de objetos `TextWriter`, como `Console.Out`, pasando un objeto `TextWriter` en el constructor: ```cs {title="Program.cs"} using var app = HttpServer.CreateBuilder() .UseConfiguration(config => { config.AccessLogsStream = new LogStream(Console.Out); }) .Build(); ``` Por cada mensaje escrito en el log basado en flujo, se llama al método `TextWriter.Flush()`. ## Formato del registro de acceso Puedes personalizar el formato del registro de acceso mediante variables predefinidas. Considera la siguiente línea: ```cs config.AccessLogsFormat = "%dd/%dmm/%dy %tH:%ti:%ts %tz %ls %ri %rs://%ra%rz%rq [%sc %sd] %lin -> %lou in %lmsms [%{user-agent}]"; ``` Escribirá un mensaje como: 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] Puedes formatear tu archivo de log según el formato descrito en la tabla: | Valor | Qué representa | Ejemplo | |-------|----------------|---------| | %dd | Día del mes (formateado con dos dígitos) | 05 | | %dmmm | Nombre completo del mes | July | | %dmm | Nombre abreviado del mes (tres letras) | Jul | | %dm | Número del mes (formateado con dos dígitos) | 07 | | %dy | Año (formateado con cuatro dígitos) | 2023 | | %th | Hora en formato de 12 horas | 03 | | %tH | Hora en formato de 24 horas (HH) | 15 | | %ti | Minutos (formateado con dos dígitos) | 30 | | %ts | Segundos (formateado con dos dígitos) | 45 | | %tm | Milisegundos (formateado con tres dígitos) | 123 | | %tz | Desplazamiento de zona horaria (total de horas en UTC) | +03:00 | | %ri | Dirección IP remota del cliente, resuelta al inicio de la solicitud | 192.168.1.100 | | %rm | Método HTTP (mayúsculas) | GET | | %rs | Esquema URI (http/https) | https | | %ra | Autoridad URI (dominio) | example.com | | %rh | Host de la solicitud | www.example.com | | %rp | Puerto de la solicitud | 443 | | %rz | Ruta de la solicitud | /path/to/resource | | %rq | Cadena de consulta | ?key=value&another=123 | | %sc | Código de estado de la respuesta HTTP | 200 | | %sd | Descripción del estado de la respuesta HTTP | OK | | %lin | Tamaño legible de la solicitud | 1.2 KB | | %linr | Tamaño bruto de la solicitud (bytes) | 1234 | | %lou | Tamaño legible de la respuesta | 2.5 KB | | %lour | Tamaño bruto de la respuesta (bytes) | 2560 | | %lms | Tiempo transcurrido en milisegundos | 120 | | %ls | Estado de ejecución | Executed | | %{header-name} | Representa el encabezado `header-name` de la solicitud. | `Mozilla/5.0 (platform; rv:gecko [...]` | | %{:header-name} | Representa el encabezado `header-name` de la respuesta. | `application/json` | | %{$key} | Representa el valor almacenado bajo `key` en [HttpContext.RequestBag](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpContext.RequestBag.md), convertido con `ToString()`. Vacío cuando la clave no está establecida. | `42` | También puedes usar `HttpServerConfiguration.DefaultAccessLogFormat` para emplear el formato de registro de acceso predeterminado. El término `%{$key}` permite que los manejadores de solicitudes y rutas añadan sus propios valores al registro de acceso: ```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"); }); ``` ## Rotación de registros Puedes configurar el servidor HTTP para rotar los archivos de log a un archivo comprimido .gz cuando alcancen un cierto tamaño. El tamaño se verifica periódicamente según el umbral que definas. ```cs LogStream errorLog = new LogStream("logs/error.log") .ConfigureRotatingPolicy( maximumSize: 64 * SizeHelper.UnitMb, dueTime: TimeSpan.FromHours(6)); ``` El código anterior comprobará cada seis horas si el archivo del `LogStream` ha alcanzado su límite de 64 MB. Si es así, el archivo se comprime a .gz y luego se limpia `access.log`. Durante este proceso, la escritura en el archivo está bloqueada hasta que el archivo se comprime y se limpia. Todas las líneas que ingresen para ser escritas en este período quedarán en una cola esperando el fin de la compresión. Esta función solo funciona con `LogStream` basados en archivos. ## Registro de errores Cuando un servidor no lanza errores al depurador, reenvía los errores a la escritura de logs cuando existen. Puedes configurar la escritura de errores con: ```cs config.ThrowExceptions = false; config.ErrorsLogsStream = new LogStream("error.log"); ``` Esta propiedad solo escribirá algo en el log si el error no es capturado por el callback o la propiedad [Router.CallbackErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.CallbackErrorHandler.md). El error escrito por el servidor siempre incluye la fecha y hora, los encabezados de la solicitud (no el cuerpo), la traza del error y la traza de la excepción interna, si existe alguna. ## Otras instancias de registro Tu aplicación puede tener cero o múltiples `LogStream`; no hay límite en la cantidad de canales de log que puede tener. Por lo tanto, es posible dirigir el log de tu aplicación a un archivo distinto del `AccessLog` o `ErrorLog` predeterminados. ```cs LogStream appMessages = new LogStream("messages.log"); appMessages.WriteLine("Application started at {0}", DateTime.Now); ``` ## Escritura segura de archivos Para archivos que no son `LogStream`, como informes o volcados de estado, [LogStream](https://docs.sisk-framework.org/api/Sisk.Core.Http.LogStream.md) ofrece ayudantes estáticos que crean el directorio padre faltante, serializan escrituras concurrentes al mismo archivo dentro del proceso y devuelven `false` en lugar de lanzar una excepción cuando la operación falla: - [LogStream.SafeWriteToFile](https://docs.sisk-framework.org/api/Sisk.Core.Http.LogStream.SafeWriteToFile.md) y [LogStream.SafeWriteToFileAsync](https://docs.sisk-framework.org/api/Sisk.Core.Http.LogStream.SafeWriteToFileAsync.md) reemplazan el contenido del archivo. - [LogStream.SafeAppendToFile](https://docs.sisk-framework.org/api/Sisk.Core.Http.LogStream.SafeAppendToFile.md) y [LogStream.SafeAppendToFileAsync](https://docs.sisk-framework.org/api/Sisk.Core.Http.LogStream.SafeAppendToFileAsync.md) añaden contenido al archivo. Cada método acepta una cadena o un arreglo de líneas y tiene sobrecargas que reciben un `Encoding`. ```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."); } ``` El bloqueo es por proceso: las escrituras de otros procesos al mismo archivo no están coordinadas. Si solo necesitas asegurarte de que un directorio exista, usa [PathHelper.EnsureDirectoryExistance](https://docs.sisk-framework.org/api/Sisk.Core.Helpers.PathHelper.EnsureDirectoryExistance.md) o [PathHelper.EnsureFileDirectoryExistance](https://docs.sisk-framework.org/api/Sisk.Core.Helpers.PathHelper.EnsureFileDirectoryExistance.md), que devuelven `true` cuando el directorio ya existía y `false` cuando se creó. ## Extender LogStream Puedes extender la clase `LogStream` para escribir formatos personalizados, compatibles con el motor de logs actual de Sisk. El ejemplo a continuación permite escribir mensajes coloridos en la consola mediante la biblioteca Spectre.Console: ```cs {title="CustomLogStream.cs"} public class CustomLogStream : LogStream { protected override void WriteLineInternal(string line) { base.WriteLineInternal($"[{DateTime.Now:g}] {line}"); } } ``` Otra forma de escribir automáticamente logs personalizados para cada solicitud/respuesta es crear un [HttpServerHandler](https://docs.sisk-framework.org/api/Sisk.Core.Http.Handlers.HttpServerHandler.md). El ejemplo a continuación es un poco más completo. Escribe el cuerpo de la solicitud y la respuesta en JSON a la consola. Puede ser útil para depurar solicitudes en general. Este ejemplo hace uso de `ContextBag` y `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) { // En este punto, la conexión está abierta y el cliente ha enviado el encabezado que indica // que el contenido es JSON. La línea siguiente lee el contenido y lo deja almacenado en la solicitud. // // Si el contenido no se lee en la acción de la solicitud, es probable que el GC recoja el contenido // después de enviar la respuesta al cliente, por lo que el contenido podría no estar disponible después de que la respuesta se cierre. // _ = request.RawBody; // Añade una pista en el contexto para indicar que esta solicitud tiene un cuerpo JSON request.Bag.Add("IsJsonRequest", true); } } protected override async void OnHttpRequestClose(HttpServerExecutionResult result) { string? requestJson = null, responseJson = null, responseMessage; if (result.Request.Bag.ContainsKey("IsJsonRequest")) { // Reformatea el JSON usando la biblioteca CypherPotato.LightJson 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 && // verifica si la respuesta es JSON httpContent.Headers.ContentType?.MediaType?.Contains("json", StringComparison.InvariantCultureIgnoreCase) == true) { string json = await httpContent.ReadAsStringAsync(); responseJson = JsonValue.Deserialize(json, new JsonOptions() { WriteIndented = true }).ToString(); } } else { // Obtiene el estado interno del manejo del servidor 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()); } } ``` --- # Eventos enviados por el servidor Source: https://docs.sisk-framework.org/es/docs/features/server-sent-events.html Sisk admite el envío de mensajes a través de Server Sent Events de forma nativa. Puedes crear conexiones desechables y persistentes, obtener las conexiones durante el tiempo de ejecución y utilizarlas. Esta característica tiene algunas limitaciones impuestas por los navegadores, como el envío solo de mensajes de texto y la imposibilidad de cerrar permanentemente una conexión. Una conexión cerrada del lado del servidor hará que el cliente intente reconectarse periódicamente cada 5 segundos (3 en algunos navegadores). Estas conexiones son útiles para enviar eventos del servidor al cliente sin que el cliente tenga que solicitar la información cada vez. ## Creando una conexión SSE Una conexión SSE funciona como una solicitud HTTP normal, pero en lugar de enviar una respuesta y cerrar inmediatamente la conexión, la conexión se mantiene abierta para enviar mensajes. Al llamar al método [HttpRequest.GetEventSource()](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.GetEventSource.md), la solicitud queda en estado de espera mientras se crea la instancia SSE. ```cs r.MapGet("/", (req) => { using var sse = req.GetEventSource(); sse.Send("Hello, world!"); return sse.Close(); }); ``` En el código anterior, creamos una conexión SSE y enviamos un mensaje "Hello, world", luego cerramos la conexión SSE del lado del servidor. > [!NOTE] > Al cerrar una conexión del lado del servidor, por defecto el cliente intentará conectarse nuevamente en ese extremo y la conexión se reiniciará, ejecutando el método de nuevo, indefinidamente. > > Es común reenviar un mensaje de terminación desde el servidor siempre que la conexión se cierre desde el servidor para evitar que el cliente intente reconectarse nuevamente. ## Añadiendo encabezados Si necesitas enviar encabezados, puedes usar el método [HttpRequestEventSource.AppendHeader](https://docs.sisk-framework.org/api/Sisk.Core.Http.Streams.HttpRequestEventSource.AppendHeader.md) antes de enviar cualquier mensaje. ```cs r.MapGet("/", (req) => { using var sse = req.GetEventSource(); sse.AppendHeader("Header-Key", "Header-value"); sse.Send("Hello!"); return sse.Close(); }); ``` Ten en cuenta que es necesario enviar los encabezados antes de enviar cualquier mensaje. ## Conexiones Wait-For-Fail Las conexiones se terminan normalmente cuando el servidor ya no puede enviar mensajes debido a una posible desconexión del cliente. Con ello, la conexión se termina automáticamente y la instancia de la clase se descarta. Incluso con una reconexión, la instancia de la clase no funcionará, ya que está vinculada a la conexión anterior. En algunas situaciones, puedes necesitar esta conexión más adelante y no deseas gestionarla mediante el método de devolución de llamada de la ruta. Para ello, podemos identificar las conexiones SSE con un identificador y obtenerlas más tarde usando dicho identificador, incluso fuera de la devolución de llamada de la ruta. Además, marcamos la conexión con [WaitForFail](https://docs.sisk-framework.org/api/Sisk.Core.Http.Streams.HttpRequestEventSource.WaitForFail.md) para no terminar la ruta y terminar la conexión automáticamente. Una conexión SSE en `WaitForFail` espera a que ocurra un error de envío causado por una desconexión, o a que transcurra la tolerancia de inactividad configurada, antes de que la ruta se reanude y cierre la conexión. ```cs r.MapGet("/", (req) => { using var sse = req.GetEventSource("my-index-connection"); sse.WaitForFail(TimeSpan.FromSeconds(15)); // esperar 15 segundos sin ningún mensaje antes de terminar la conexión return sse.Close(); }); ``` El método anterior creará la conexión, la gestionará y esperará a una desconexión o error. ```cs HttpRequestEventSource? evs = server.EventSources.GetByIdentifier("my-index-connection"); if (evs != null) { // la conexión sigue viva evs.Send("Hello again!"); } ``` Y el fragmento anterior intentará buscar la conexión recién creada y, si existe, enviará un mensaje a ella. Todas las conexiones activas del servidor que estén identificadas estarán disponibles en la colección [HttpServer.EventSources](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServer.EventSources.md). Esta colección solo almacena conexiones activas e identificadas. Las conexiones cerradas se eliminan de la colección. > [!NOTE] > Es importante notar que el keep alive tiene un límite establecido por componentes que pueden estar conectados a Sisk de forma incontrolable, como un proxy web, un kernel HTTP o un controlador de red, y cierran las conexiones inactivas después de un cierto período de tiempo. > > Por lo tanto, es importante mantener la conexión abierta enviando pings periódicos o ampliando el tiempo máximo antes de que la conexión se cierre. Lee la siguiente sección para comprender mejor el envío de pings periódicos. ## Configurar la política de ping de conexiones La política de ping es una forma automatizada de enviar mensajes periódicos a tu cliente. Esta función permite al servidor saber cuándo el cliente se ha desconectado de esa conexión sin tener que mantener la conexión abierta indefinidamente. ```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(); } ``` En el código anterior, cada 5 segundos se enviará un nuevo mensaje de ping al cliente. Esto mantendrá viva la conexión TCP y evitará que se cierre por inactividad. Además, cuando un mensaje no se puede enviar, la conexión se cierra automáticamente, liberando los recursos utilizados por la conexión. Utiliza [SendAsync](https://docs.sisk-framework.org/api/Sisk.Core.Http.Streams.HttpRequestEventSource.SendAsync.md) y [CloseAsync](https://docs.sisk-framework.org/api/Sisk.Core.Http.Streams.HttpRequestEventSource.CloseAsync.md) en rutas asíncronas. Si necesitas descartar eventos en cola antes de cerrar, llama a [Cancel](https://docs.sisk-framework.org/api/Sisk.Core.Http.Streams.HttpRequestEventSource.Cancel.md). ## Consultar conexiones Puedes buscar conexiones activas usando un predicado sobre el identificador de la conexión, para poder difundir, por ejemplo. ```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-'"); } ``` También puedes usar el método [All](https://docs.sisk-framework.org/api/Sisk.Core.Http.Streams.HttpEventSourceCollection.All.md) para obtener todas las conexiones SSE activas. --- # Sockets Web Source: https://docs.sisk-framework.org/es/docs/features/websockets.html Sisk también soporta websockets, como recibir y enviar mensajes a su cliente. Esta característica funciona bien en la mayoría de los navegadores, pero en Sisk sigue siendo experimental. Por favor, si encuentras algún error, repórtalo en GitHub. ## Accepting messages Los mensajes WebSocket se reciben en orden, encolados hasta que los procesa `ReceiveMessageAsync`. Este método no devuelve ningún mensaje cuando se alcanza el tiempo de espera, cuando la operación se cancela o cuando el cliente se desconecta. El tamaño máximo de un mensaje recibido es [HttpServerConfiguration.MaximumContentLength](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.MaximumContentLength.md), que por defecto es 100 MB desde Sisk 1.7. Cuando un cliente envía un mensaje más grande, la conexión se cierra con el estado `MessageTooBig` y `ReceiveMessageAsync` no devuelve ningún mensaje. Solo puede ocurrir una operación de lectura o escritura simultáneamente, por lo tanto, mientras esperas un mensaje con `ReceiveMessageAsync`, no es posible escribir al cliente conectado. ```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(); }); ``` ## Persistent connection El ejemplo a continuación muestra cómo usar una conexión websocket persistente, donde recibes los mensajes, los procesas y finalizas el uso del socket. ```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 Policy Similar a cómo funciona la política de ping en Server Side Events, también puedes configurar una política de ping para mantener la conexión TCP abierta si hay inactividad. ```cs ws.PingPolicy.Start( dataMessage: "ping-message", interval: TimeSpan.FromSeconds(10)); ``` ## Managed connections Al aceptar un WebSocket, puedes proporcionar un identificador. Los sockets identificados se registran en [HttpServer.WebSockets](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServer.WebSockets.md), lo que permite al servidor encontrar conexiones activas fuera de la ruta que los aceptó. ```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(); }); ``` Desde otra parte de la aplicación, consulta la colección por identificador o predicado: ```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"); } ``` Cada `HttpWebSocket` expone `Identifier`, `State`, `IsClosed` y `PingPolicy`. La colección también expone `All()`, `Find(...)`, `GetByIdentifier(...)`, `ActiveConnections` y `DropAll()` para estrategias de conexión gestionadas por el servidor. --- # Sintaxis de descarte Source: https://docs.sisk-framework.org/es/docs/features/discard-syntax.html El servidor HTTP se puede utilizar para escuchar una solicitud de devolución de llamada desde una acción, como la autenticación OAuth, y se puede descartar después de recibir esa solicitud. Esto puede ser útil en casos donde necesite una acción en segundo plano pero no desee configurar una aplicación HTTP completa para ello. El siguiente ejemplo muestra cómo crear un servidor HTTP de escucha en el puerto 5555 con [CreateListener](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServer.CreateListener.md) y esperar el siguiente contexto: ```csharp using (var server = HttpServer.CreateListener(5555)) { // esperar la siguiente solicitud http var context = await server.WaitNextAsync(); Console.WriteLine($"Ruta solicitada: {context.Request.Path}"); } ``` La función [WaitNext](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServer.WaitNext.md) espera el siguiente contexto de un procesamiento de solicitud completado. Una vez que se obtiene el resultado de esta operación, el servidor ya ha manejado completamente la solicitud y ha enviado la respuesta al cliente. --- # Inyección de dependencias Source: https://docs.sisk-framework.org/es/docs/features/instancing.html Es común dedicar miembros y instancias que duran toda la vida de una solicitud, como una conexión a una base de datos, un usuario autenticado o un token de sesión. Una de las posibilidades es a través de [HttpContext.RequestBag](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpContext.md), que crea un diccionario que dura toda la vida de una solicitud. Este diccionario puede ser accedido por [manejadores de solicitudes](https://docs.sisk-framework.org/es/docs/fundamentals/request-handlers.md) y definir variables a lo largo de esa solicitud. Por ejemplo, un manejador de solicitud que autentica a un usuario establece este usuario dentro del `HttpContext.RequestBag`, y dentro de la lógica de la solicitud, este usuario puede ser recuperado con `HttpContext.RequestBag.Get()`. Los objetos definidos en este diccionario están limitados al ciclo de vida de la solicitud. Se eliminan al final de la solicitud. No necesariamente, el envío de una respuesta define el final del ciclo de vida de la solicitud. Cuando se ejecutan [manejadores de solicitudes](https://docs.sisk-framework.org/es/docs/fundamentals/request-handlers.md) que se ejecutan después de enviar una respuesta, los objetos `RequestBag` todavía existen y no han sido eliminados. Aquí hay un ejemplo: ```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; // avanzar al siguiente manejador de solicitud o lógica de solicitud } } ``` ```csharp {title="Controllers/HelloController.cs"} [RouteGet("/hello")] [RequestHandler] public HttpResponse SayHello(HttpRequest request) { var authenticatedUser = request.Bag.Get(); return new HttpResponse() { Content = new StringContent($"Hola {authenticatedUser.Name}!") }; } ``` Este es un ejemplo preliminar de esta operación. La instancia de `User` se creó dentro del manejador de solicitud dedicado a la autenticación, y todas las rutas que utilizan este manejador de solicitud tendrán la garantía de que habrá un `User` en su instancia de `HttpContext.RequestBag`. Es posible definir lógica para obtener instancias cuando no se han definido previamente en el `RequestBag` a través de métodos como [GetOrAdd](https://docs.sisk-framework.org/api/Sisk.Core.Entity.TypedValueDictionary.GetOrAdd.md) o [GetOrAddAsync](https://docs.sisk-framework.org/api/Sisk.Core.Entity.TypedValueDictionary.GetOrAddAsync.md). Desde la versión 1.3, se introdujo la propiedad estática [HttpContext.Current](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpContext.Current.md), que permite acceder al `HttpContext` actualmente en ejecución del contexto de la solicitud. Esto permite exponer miembros del `HttpContext` fuera de la solicitud actual y definir instancias en objetos de ruta. El ejemplo siguiente define un controlador que tiene miembros comúnmente accedidos por el contexto de una solicitud. ```csharp {title="Controllers/Controller.cs"} public abstract class Controller : RouterModule { // Obtener la instancia existente o crear una nueva instancia de base de datos para esta solicitud protected DbContext Database => HttpContext.Current.RequestBag.GetOrAdd(() => new DbContext()); // La carga diferida de repositorios también es común 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)); // La siguiente línea lanzará una excepción si la propiedad se accede cuando el Usuario no // está definido en la bolsa de solicitudes protected User AuthenticatedUser => => HttpContext.Current.RequestBag.Get(); // Exponer la instancia de HttpRequest también es compatible protected HttpRequest Request => HttpContext.Current.Request } ``` Y definir tipos que hereden del controlador: ```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; } } ``` Para el ejemplo anterior, necesitarás configurar un [manejador de valor](https://docs.sisk-framework.org/es/docs/fundamentals/responses.md#implicit-response-types) en tu enrutador para que los objetos devueltos por el enrutador se transformen en una respuesta [HttpResponse](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpResponse.md) válida. Tenga en cuenta que los métodos no tienen un argumento `HttpRequest request` como está presente en otros métodos. Esto se debe a que, desde la versión 1.3, el enrutador admite dos tipos de delegados para respuestas de enrutamiento: [RouteAction](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouteAction.md), que es el delegado predeterminado que recibe un argumento `HttpRequest`, y [ParameterlessRouteAction](https://docs.sisk-framework.org/api/Sisk.Core.Routing.ParameterlessRouteAction.md). El objeto `HttpRequest` todavía se puede acceder a través de la propiedad [Request](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpContext.Request.md) de la propiedad estática `HttpContext` en el subproceso. En el ejemplo anterior, definimos un objeto desechable, el `DbContext`, y necesitamos asegurarnos de que todas las instancias creadas en un `DbContext` se eliminen cuando la sesión HTTP finalice. Para esto, podemos utilizar dos formas de lograrlo. Una es crear un [manejador de solicitud](https://docs.sisk-framework.org/es/docs/fundamentals/request-handlers.md) que se ejecute después de la acción del enrutador, y la otra forma es a través de un [manejador de servidor personalizado](https://docs.sisk-framework.org/es/docs/advanced/http-server-handlers.md). Para el primer método, podemos crear el manejador de solicitud directamente en el método [OnSetup](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouterModule.OnSetup.md) heredado de `RouterModule`: ```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) => { // obtener una instancia de DbContext definida en el contexto del manejador de solicitud y // eliminarla ctx.RequestBag.GetOrDefault()?.Dispose(); return null; }, executionMode: RequestHandlerExecutionMode.AfterResponse)); } } ``` > [!TIP] > > Desde la versión 1.4 de Sisk, se introduce la propiedad [HttpServerConfiguration.DisposeDisposableContextValues](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.DisposeDisposableContextValues.md) y se habilita de forma predeterminada, que define si el servidor HTTP debe eliminar todos los valores `IDisposable` en la bolsa de contexto cuando se cierra una sesión HTTP. El método anterior garantizará que el `DbContext` se elimine cuando se finalice la sesión HTTP. Puedes hacer esto para más miembros que necesitan ser eliminados al final de una respuesta. Para el segundo método, puedes crear un [manejador de servidor personalizado](https://docs.sisk-framework.org/es/docs/advanced/http-server-handlers.md) que eliminará el `DbContext` cuando se finalice la sesión HTTP. ```csharp {title="Server/Handlers/ObjectDisposerHandler.cs"} public class ObjectDisposerHandler : HttpServerHandler { protected override void OnHttpRequestClose(HttpServerExecutionResult result) { result.Context.RequestBag.GetOrDefault()?.Dispose(); } } ``` Y utilízalo en tu constructor de aplicaciones: ```csharp {title="Program.cs"} using var host = HttpServer.CreateBuilder() .UseHandler() .Build(); ``` Esta es una forma de controlar la limpieza de código y mantener las dependencias de una solicitud separadas por el tipo de módulo que se utilizará, reduciendo la cantidad de código duplicado dentro de cada acción de un enrutador. Es una práctica similar a la que se utiliza la inyección de dependencias en frameworks como ASP.NET. --- # Transmisión de contenido Source: https://docs.sisk-framework.org/es/docs/features/content-streaming.html El Sisk admite la lectura y el envío de flujos de contenido desde y hacia el cliente. Esta característica es útil para eliminar la sobrecarga de memoria para serializar y deserializar contenido durante la vida útil de una solicitud. ## Flujo de contenido de la solicitud Los contenidos pequeños se cargan automáticamente en la memoria del búfer de conexión HTTP, cargando rápidamente este contenido en [HttpRequest.Body](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Body.md) y [HttpRequest.RawBody](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.RawBody.md). Para contenidos más grandes, se puede utilizar el método [HttpRequest.GetRequestStream](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.GetRequestStream.md) para obtener el flujo de lectura de contenido de la solicitud. Es importante destacar que el método [HttpRequest.GetMultipartFormContent](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.GetMultipartFormContent.md) lee todo el contenido de la solicitud en memoria, por lo que puede no ser útil para leer contenidos grandes. Consideremos el siguiente ejemplo: ```csharp {title="Controller/UploadDocument.cs"} [RoutePost ( "/api/upload-document/" )] public async Task UploadDocument ( HttpRequest request ) { var fileName = request.RouteParameters [ "filename" ].GetString (); if (!request.HasContents) { // la solicitud no tiene contenido 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 = "Archivo enviado con éxito." } ) }; } ``` En el ejemplo anterior, el método `UploadDocument` lee el contenido de la solicitud y lo guarda en un archivo. No se realiza ninguna asignación de memoria adicional excepto por el búfer de lectura utilizado por `Stream.CopyToAsync`. El ejemplo anterior elimina la presión de asignación de memoria para un archivo muy grande, lo que puede optimizar el rendimiento de la aplicación. Es una buena práctica utilizar siempre un [CancellationToken](https://learn.microsoft.com/pt-br/dotnet/api/system.threading.cancellationtoken) en una operación que pueda ser larga, como el envío de archivos, ya que depende de la velocidad de la red entre el cliente y el servidor. El ajuste con un CancellationToken se puede realizar de la siguiente manera: ```csharp {title="Controller/UploadDocument.cs"} // el token de cancelación a continuación lanzará una excepción si se alcanza el tiempo de espera de 30 segundos. 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 = "La carga superó el tiempo de carga máximo (30 segundos)." } ) }; } ``` ## Flujo de contenido de la respuesta Enviar contenido de respuesta también es posible. Actualmente, hay dos formas de hacerlo: a través del método [HttpRequest.GetResponseStream](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.GetResponseStream.md) y utilizando un contenido de tipo [StreamContent](https://learn.microsoft.com/pt-br/dotnet/api/system.net.http.streamcontent?view=net-9.0). Consideremos un escenario en el que necesitamos servir un archivo de imagen. Para hacer esto, podemos utilizar el siguiente código: ```csharp {title="Controller/ImageController.cs"} [RouteGet ( "/api/profile-picture" )] public async Task UploadDocument ( HttpRequest request ) { // método de ejemplo para obtener una imagen de perfil 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}" } }; } ``` El método anterior realiza una asignación de memoria cada vez que se lee el contenido de la imagen. Si la imagen es grande, esto puede causar un problema de rendimiento, y en situaciones de pico, incluso una sobrecarga de memoria y hacer que el servidor se bloquee. En estas situaciones, la caché puede ser útil, pero no eliminará el problema, ya que la memoria seguirá reservada para ese archivo. La caché aliviará la presión de tener que asignar memoria para cada solicitud, pero para archivos grandes, no será suficiente. Enviar la imagen a través de un flujo puede ser una solución al problema. En lugar de leer todo el contenido de la imagen, se crea un flujo de lectura en el archivo y se copia al cliente utilizando un búfer pequeño. #### Enviar a través del método GetResponseStream El método [HttpRequest.GetResponseStream](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.GetResponseStream.md) crea un objeto que permite enviar trozos de la respuesta HTTP a medida que se prepara el flujo de contenido. Este método es más manual, requiriendo que se defina el estado, los encabezados y el tamaño del contenido antes de enviar el contenido. ```csharp {title="Controller/ImageController.cs"} [RouteGet ( "/api/profile-picture" )] public async Task UploadDocument ( HttpRequest request ) { var profilePictureFilename = "profile-picture.jpg"; // en esta forma de envío, el estado y el encabezado deben definirse // antes de enviar el contenido 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 )) { // en esta forma de envío, también es necesario definir el tamaño del contenido // antes de enviarlo. requestStreamManager.SetContentLength ( fs.Length ); // si no se conoce el tamaño del contenido, se puede utilizar codificación por trozos // para enviar el contenido requestStreamManager.SendChunked = true; // y luego, escribir en el flujo de salida await fs.CopyToAsync ( requestStreamManager.ResponseStream ); } } ``` #### Enviar contenido a través de un StreamContent La clase [StreamContent](https://learn.microsoft.com/pt-br/dotnet/api/system.net.http.streamcontent?view=net-9.0) permite enviar contenido desde una fuente de datos como un flujo de bytes. Esta forma de envío es más fácil, eliminando los requisitos anteriores, e incluso permitiendo el uso de [codificación de compresión](https://docs.sisk-framework.org/es/docs/fundamentals/responses.md#gzip-deflate-and-brotli-compression) para reducir el tamaño del contenido. ```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] > > En este tipo de contenido, no encapsule el flujo en un bloque `using`. El contenido se descartará automáticamente por el servidor HTTP cuando se finalice el flujo de contenido, con o sin errores. --- # Habilitando CORS (Compartir Recursos entre Orígenes) en Sisk Source: https://docs.sisk-framework.org/es/docs/features/cors.html Sisk tiene una herramienta que puede ser útil para manejar [Cross-Origin Resource Sharing (CORS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS) al exponer tu servicio públicamente. Esta característica no forma parte del protocolo HTTP sino que es una característica específica de los navegadores web definida por el W3C. Este mecanismo de seguridad impide que una página web realice peticiones a un dominio diferente al que proporcionó la página web. Un proveedor de servicios puede permitir que ciertos dominios accedan a sus recursos, o solo a uno. ## Same Origin Para que un recurso se identifique como "misma origen", una solicitud debe incluir el encabezado [Origin](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Origin) en su petición: ```http GET /api/users HTTP/1.1 Host: example.com Origin: http://example.com ... ``` Y el servidor remoto debe responder con un encabezado [Access-Control-Allow-Origin](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin) con el mismo valor que el origen solicitado: ```http HTTP/1.1 200 OK Access-Control-Allow-Origin: http://example.com ... ``` Esta verificación es **explícita**: el host, puerto y protocolo deben ser los mismos que los solicitados. Revisa el ejemplo: - Un servidor responde que su `Access-Control-Allow-Origin` es `https://example.com`: - `https://example.net` - el dominio es diferente. - `http://example.com` - el esquema es diferente. - `http://example.com:5555` - el puerto es diferente. - `https://www.example.com` - el host es diferente. En la especificación, solo se permite la sintaxis para ambos encabezados, tanto en solicitudes como en respuestas. La ruta de la URL se ignora. El puerto también se omite si es un puerto predeterminado (80 para HTTP y 443 para HTTPS). ```http Origin: null Origin: :// Origin: ://: ``` ## Enabling CORS De forma nativa, dispones del objeto [CrossOriginResourceSharingHeaders](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.md) dentro de tu [ListeningHost](https://docs.sisk-framework.org/api/Sisk.Core.Http.ListeningHost.md). Puedes configurar CORS al inicializar el servidor: ```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(); } ``` El código anterior enviará los siguientes encabezados para **todas las respuestas**: ```http HTTP/1.1 200 OK Access-Control-Allow-Origin: http://example.com Access-Control-Allow-Headers: Authorization Access-Control-Expose-Headers: Content-Type ``` Estos encabezados deben enviarse para todas las respuestas a un cliente web, incluidos errores y redirecciones. Puede que notes que la clase [CrossOriginResourceSharingHeaders](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.md) tiene dos propiedades similares: [AllowOrigin](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AllowOrigin.md) y [AllowOrigins](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AllowOrigins.md). Ten en cuenta que una es plural y la otra singular. - La propiedad **AllowOrigin** es estática: solo el origen que especifiques será enviado para todas las respuestas. - La propiedad **AllowOrigins** es dinámica: el servidor verifica si el origen de la solicitud está contenido en esta lista. Si se encuentra, se envía en la respuesta de ese origen. Cuando el origen permitido depende de la solicitud, es decir, cuando utilizas **AllowOrigins** o el valor [AutoAllowOrigin](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AutoAllowOrigin.md) descrito a continuación, Sisk también envía el encabezado `Vary: Origin` para que las cachés no reutilicen una respuesta para un origen diferente. ### Wildcards and automatic headers Alternativamente, puedes usar un comodín (`*`) en el origen de la respuesta para especificar que cualquier origen está permitido para acceder al recurso. Sin embargo, este valor no está permitido para solicitudes que incluyen credenciales (encabezados de autorización) y esta operación [resultará en un error](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS/Errors/CORSNotSupportingCredentials). Puedes solucionar este problema enumerando explícitamente qué orígenes estarán permitidos mediante la propiedad [AllowOrigins](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AllowOrigins.md) o también usar la constante [AutoAllowOrigin](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AutoAllowOrigin.md) en el valor de [AllowOrigin](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AllowOrigin.md). Esta propiedad mágica definirá el encabezado `Access-Control-Allow-Origin` con el mismo valor que el encabezado `Origin` de la solicitud. También puedes usar [AutoFromRequestMethod](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AutoFromRequestMethod.md) y [AutoFromRequestHeaders](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AutoFromRequestHeaders.md) para un comportamiento similar a `AllowOrigin`, que responde automáticamente según los encabezados enviados. ```csharp using var host = HttpServer.CreateBuilder() .UseCors(new CrossOriginResourceSharingHeaders( // Responde según el encabezado Origin de la solicitud allowOrigin: CrossOriginResourceSharingHeaders.AutoAllowOrigin, // Responde según el encabezado Access-Control-Request-Method o el método de la solicitud allowMethods: [CrossOriginResourceSharingHeaders.AutoFromRequestMethod], // Responde según el encabezado Access-Control-Request-Headers o los encabezados enviados allowHeaders: [CrossOriginResourceSharingHeaders.AutoFromRequestHeaders], exposeHeaders: [HttpKnownHeaderNames.ContentType, "X-Authenticated-Account-Id"], allowCredentials: true)) .Build(); ``` ## Other Ways to Apply CORS Si estás trabajando con [service providers](https://docs.sisk-framework.org/es/docs/extensions/service-providers.md), puedes sobrescribir los valores definidos en el archivo de configuración: ```csharp static async Task Main(string[] args) { using var app = HttpServer.CreateBuilder() .UsePortableConfiguration(...) .UseCors(cors => { // Sobrescribirá el origen definido en la configuración // del archivo. cors.AllowOrigin = "http://example.com"; }) .Build(); await app.StartAsync(); } ``` ## Disabling CORS on Specific Routes La propiedad `UseCors` está disponible tanto para rutas como para todos los atributos de ruta y puede desactivarse con el siguiente ejemplo: ```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" }; } } ``` ## Replacing Values in the Response Puedes reemplazar o eliminar valores explícitamente en una acción del router: ```csharp [RoutePrefix("api/widgets")] public class WidgetController : Controller { public IEnumerable GetWidgets(HttpRequest request) { // Elimina el encabezado Access-Control-Allow-Credentials request.Context.OverrideHeaders.AccessControlAllowCredentials = string.Empty; // Reemplaza el encabezado Access-Control-Allow-Origin request.Context.OverrideHeaders.AccessControlAllowOrigin = "https://contorso.com"; return new[] { "Green widget", "Red widget" }; } } ``` ## Preflight Requests Una solicitud de preflight es una petición [OPTIONS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Methods/OPTIONS) que el cliente envía antes de la solicitud real. Cuando una solicitud con el método `OPTIONS` coincide con la ruta de un endpoint, pero no hay una ruta configurada explícitamente para `OPTIONS`, el servidor Sisk responde con un `200 OK` y los encabezados CORS aplicables, y luego el cliente puede continuar con la solicitud real. - Si una ruta está mapeada explícitamente con [RouteMethod.Options](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouteMethod.md), por ejemplo con `MapOptions`, esa ruta maneja la solicitud en lugar de la respuesta automática. - Si la ruta que coincide con la ruta tiene `UseCors = false`, la respuesta automática aún se envía, pero sin encabezados CORS. > [!NOTE] > Antes de Sisk 1.7, la respuesta automática de preflight siempre incluía los encabezados CORS, incluso para rutas con `UseCors = false`. ## Disabling CORS Globally No es posible hacer esto. Para no usar CORS, simplemente no lo configures. --- # Servidor de Archivos Source: https://docs.sisk-framework.org/es/docs/features/file-server.html Sisk proporciona el espacio de nombres `Sisk.Http.FileSystem`, que contiene herramientas para servir archivos estáticos, listar directorios y convertir archivos. Esta característica le permite servir archivos desde un directorio local, con soporte para solicitudes de rango (transmisión de audio/video) y procesamiento de archivos personalizado. ## Servir archivos estáticos La forma más sencilla de servir archivos estáticos es [Router.MapFileSystem](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.MapFileSystem.md). Este método asigna un prefijo de URL a un directorio en disco. ```cs using Sisk.Core.Http; using Sisk.Core.Http.FileSystem; // asigna la raíz del servidor al directorio actual mainRouter.MapFileSystem("/", Directory.GetCurrentDirectory()); // asigna /assets a la carpeta "public/assets" mainRouter.MapFileSystem( "/assets", Path.Combine(Directory.GetCurrentDirectory(), "public", "assets")); ``` Cuando una solicitud coincide con el prefijo de ruta, el `HttpFileServerHandler` buscará un archivo en el directorio especificado. Si lo encuentra, servirá el archivo; de lo contrario, devolverá una respuesta 404 (o 403 si se niega el acceso). `HttpFileServer.CreateServingRoute` sigue disponible cuando necesita crear un objeto `Route` explícitamente, pero `MapFileSystem` es la opción más directa para el código de la aplicación. Ambos métodos crean una [PrefixRoute](https://docs.sisk-framework.org/api/Sisk.Core.Routing.PrefixRoute.md) (una ruta regex antes de Sisk 1.7), que es una [ruta dinámica](https://docs.sisk-framework.org/es/docs/fundamentals/routing.md#matching-precedence). Las rutas estáticas siempre se prueban antes, por lo que un servidor de archivos asignado a `/` funciona como alternativa para las rutas que sus otras rutas no manejan. Las rutas del servidor de archivos no se ven afectadas por [HttpServerConfiguration.ForceTrailingSlash](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ForceTrailingSlash.md). ## HttpFileServerHandler Para tener más control sobre cómo se sirven los archivos, puede instanciar y configurar `HttpFileServerHandler` manualmente. ```cs var fileHandler = new HttpFileServerHandler("/var/www/html"); // habilita el listado de directorios (deshabilitado por defecto) fileHandler.AllowDirectoryListing = true; // establece un prefijo de ruta personalizado (esto se recortará del camino de la solicitud) fileHandler.RoutePrefix = "/public"; // registra el manejador bajo /public mainRouter.MapFileSystem("/public", fileHandler); ``` ### Configuración | Propiedad | Descripción | |---|---| | `RootDirectoryPath` | La ruta absoluta o relativa al directorio raíz desde el cual se sirven los archivos. | | `RoutePrefix` | El prefijo de ruta que se recortará del camino de la solicitud al resolver archivos. El valor predeterminado es `/`. | | `AllowDirectoryListing` | Si se establece en `true`, habilita el listado de directorios cuando se solicita un directorio y no se encuentra un archivo índice. El valor predeterminado es `false`. | | `FileConverters` | Una lista de `HttpFileServerFileConverter` utilizada para transformar archivos antes de servirlos. | ## Listado de Directorios Cuando `AllowDirectoryListing` está habilitado y el usuario solicita una ruta de directorio, Sisk generará una página HTML que lista el contenido de ese directorio. El listado de directorios incluye: - Navegación al directorio padre (`..`). - Lista de subdirectorios. - Lista de archivos con tamaño y fecha de última modificación. ## Convertidores de Archivos Los convertidores de archivos le permiten interceptar tipos de archivo específicos y manejarlos de forma diferente. Por ejemplo, podría querer transcodificar una imagen, comprimir un archivo al vuelo, o servir un archivo usando contenido parcial (solicitudes de rango). Sisk incluye dos convertidores integrados para transmisión de medios: - `HttpFileAudioConverter`: Maneja `.mp3`, `.ogg`, `.wav`, `.flac`, `.ogv`. - `HttpFileVideoConverter`: Maneja `.webm`, `.avi`, `.mkv`, `.mpg`, `.mpeg`, `.wmv`, `.mov`, `.mp4`. Estos convertidores habilitan el soporte para **solicitudes de rango HTTP**, permitiendo a los clientes buscar dentro de archivos de audio y video. ### Crear un convertidor personalizado Para crear un convertidor de archivos personalizado, herede de `HttpFileServerFileConverter` e implemente `CanConvert` y `Convert`. ```cs using Sisk.Core.Http; using Sisk.Core.Http.FileSystem; public class MyTextConverter : HttpFileServerFileConverter { public override bool CanConvert(FileInfo file) { // aplicar solo a archivos .txt return file.Extension.Equals(".txt", StringComparison.OrdinalIgnoreCase); } public override HttpResponse Convert(FileInfo file, HttpRequest request) { string content = File.ReadAllText(file.FullName); // convertir todo el contenido de texto a mayúsculas return new HttpResponse(200) { Content = new StringContent(content.ToUpper()) }; } } ``` Luego, agréguelo a su manejador: ```cs var handler = new HttpFileServerHandler("./files"); handler.FileConverters.Add(new MyTextConverter()); ``` --- # Protocolo de Contexto de Modelo Source: https://docs.sisk-framework.org/es/docs/extensions/mcp.html Es posible crear aplicaciones que proporcionen contexto a modelos de agente usando grandes modelos de lenguaje (LLM) mediante el paquete [Sisk.ModelContextProtocol](https://www.nuget.org/packages/Sisk.ModelContextProtocol/) : dotnet add package Sisk.ModelContextProtocol --prerelease Este paquete expone clases y métodos útiles para construir servidores MCP que funcionan sobre [Streamable HTTP](https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http), soportando mensajes de solicitud/respuesta únicos. Desde la versión 1.7.0-rc1, soporta solicitudes sin estado de la versión de protocolo `2026-07-28` y mantiene el flujo de inicialización heredado para clientes que usan las versiones `2025-11-25`, `2025-06-18` y `2025-03-26`. Soporta herramientas y habilidades. > [!NOTE] > > Antes de comenzar, ten en cuenta que este paquete está en desarrollo y puede presentar comportamientos que no se ajustan a la especificación. Lee los [detalles del paquete](https://github.com/sisk-http/core/tree/main/extensions/Sisk.ModelContextProtocol) para saber qué está en desarrollo y qué aún no funciona. ## Comenzando con MCP La clase [McpProvider](https://docs.sisk-framework.org/api/Sisk.ModelContextProtocol.McpProvider.md) es el punto de entrada para definir un servidor MCP. Es un objeto proveedor sellado que puede configurarse al iniciar. Tu aplicación Sisk puede tener uno o más proveedores MCP. ```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}")); })); ``` Si tu aplicación proporcionará solo un proveedor MCP, puedes usar el singleton del constructor: ```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(); } ``` El punto final debe aceptar tanto solicitudes `GET` como `POST`, por lo que `MapAny` es el mapeo de ruta más sencillo. `HandleMcpRequestAsync` devuelve una [HttpResponse](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpResponse.md), y tu ruta debe devolverla. Si necesitas varios proveedores en una sola aplicación, omite el singleton y llama directamente a [McpProvider.HandleRequestAsync](https://docs.sisk-framework.org/api/Sisk.ModelContextProtocol.McpProvider.HandleRequestAsync.md) desde cada ruta: ```csharp var mathProvider = new McpProvider("math-server", "Mathematics server", new Version(1, 0)); router.MapAny("/mcp/math", async request => { return await mathProvider.HandleRequestAsync(request); }); ``` ## Creando esquemas JSON para funciones La biblioteca [Sisk.ModelContextProtocol] utiliza un fork de [LightJson](https://github.com/CypherPotato/LightJson) para la manipulación de JSON y esquemas JSON. Esta implementación proporciona un generador fluido de esquemas JSON para varios objetos: - JsonSchema.CreateObjectSchema - JsonSchema.CreateArraySchema - JsonSchema.CreateBooleanSchema - JsonSchema.CreateNumberSchema - JsonSchema.CreateStringSchema - JsonSchema.Empty Ejemplo: ```csharp JsonSchema.CreateObjectSchema( properties: new Dictionary() { { "numbers", JsonSchema.CreateArraySchema( itemsSchema: JsonSchema.CreateNumberSchema(), minItems: 1, description: "The numbers to sum.") } }, requiredProperties: ["numbers"]); ``` Produce el siguiente esquema: ```json { "type": "object", "properties": { "numbers": { "type": "array", "items": { "type": "number" }, "minItems": 1, "description": "The numbers to sum." } }, "required": ["numbers"] } ``` ## Manejo de llamadas a funciones La función definida en el parámetro `executionHandler` de [McpTool](https://docs.sisk-framework.org/api/Sisk.ModelContextProtocol.McpTool.md) proporciona un JsonObject que contiene los argumentos de la llamada y que puede leerse de forma fluida: ```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) => { // leer el nombre de la acción. lanzará una excepción si es nulo o no es una cadena explícita string actionName = context.Arguments["action_name"].GetString(); // action_data está definido como no requerido, por lo que puede ser nulo aquí string? actionData = context.Arguments["action_data"].MaybeNull()?.GetString(); // Manejar la acción del navegador basándose en actionName return await Task.FromResult( McpToolResult.CreateText($"Performed browser action: {actionName}")); })); ``` Los argumentos de la herramienta se validan contra el esquema antes de que se ejecute tu manejador. Si la validación falla, el proveedor devuelve un resultado de error al cliente MCP y no invoca el manejador de la herramienta. ## Resultados de funciones El objeto [McpToolResult](https://docs.sisk-framework.org/api/Sisk.ModelContextProtocol.McpToolResult.md) ofrece tres métodos para crear contenido para una respuesta de herramienta: - [CreateAudio(ReadOnlySpan, string)](https://docs.sisk-framework.org/api/Sisk.ModelContextProtocol.McpToolResult.CreateAudio.md): crea una respuesta basada en audio para el cliente MCP. - [CreateImage(ReadOnlySpan, string)](https://docs.sisk-framework.org/api/Sisk.ModelContextProtocol.McpToolResult.CreateImage.md): crea una respuesta basada en imagen para el cliente MCP. - [CreateText(string)](https://docs.sisk-framework.org/api/Sisk.ModelContextProtocol.McpToolResult.CreateText.md): crea una respuesta basada en texto (por defecto) para el cliente MCP. Además, es posible combinar varios contenidos diferentes en una única respuesta JSON de herramienta: ```csharp mcp.Tools.Add(new McpTool( ... executionHandler: async (McpToolContext context) => { // simular trabajo real byte[] browserScreenshot = await browser.ScreenshotAsync(); return McpToolResult.Combine( McpToolResult.CreateText("Heres the screenshot of the browser:"), McpToolResult.CreateImage(browserScreenshot, "image/png") ) })); ``` El proveedor maneja actualmente `server/discover`, los métodos heredados `initialize` y `ping`, `tools/list`, `tools/call`, `skills/list`, `skills/get`, `resources/list`, `resources/read` y notificaciones. Los métodos JSON‑RPC no soportados devuelven una respuesta de error JSON‑RPC. ## Publicación de habilidades Un proveedor MCP también puede publicar [Agent Skills](https://github.com/modelcontextprotocol/ext-skills) a través de la extensión `io.modelcontextprotocol/skills`. Añade objetos [McpSkill](https://docs.sisk-framework.org/api/Sisk.ModelContextProtocol.McpSkill.md) a [McpProvider.Skills](https://docs.sisk-framework.org/api/Sisk.ModelContextProtocol.McpProvider.Skills.md) antes de que el proveedor comience a manejar solicitudes: ```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` genera el archivo `SKILL.md` a partir del frontmatter y del cuerpo Markdown. El último segmento de `path` debe ser igual al campo `name` del frontmatter. Los archivos de la habilidad se copian cuando la habilidad se crea y nunca se leen del disco por el proveedor. Los clientes listan y leen habilidades con `skills/list`, `skills/get` y `resources/read`, usando la versión de protocolo `2026-07-28`. Consulta los [detalles del paquete](https://github.com/sisk-http/core/tree/main/extensions/Sisk.ModelContextProtocol) para conocer los límites y reglas de validación. ## Trabajo continuo El Protocolo de Contexto de Modelo es un protocolo de comunicación para modelos de agente y aplicaciones que les proporcionan contenido. Es un protocolo nuevo, por lo que es común que su especificación se actualice constantemente con deprecaciones, nuevas funcionalidades y cambios incompatibles. Es fundamental comprender los problemas que resuelve el [Protocolo de Contexto de Modelo](https://modelcontextprotocol.io/docs/getting-started/intro) antes de comenzar a crear aplicaciones de agente. También lee la especificación del paquete [Sisk.ModelContextProtocol](https://github.com/sisk-http/core/tree/main/extensions/Sisk.ModelContextProtocol) para entender su progreso, estado y lo que se puede hacer con él. --- # Extensión JSON-RPC Source: https://docs.sisk-framework.org/es/docs/extensions/json-rpc.html Sisk tiene un módulo experimental para una API [JSON-RPC 2.0](https://www.jsonrpc.org/specification), que le permite crear aplicaciones aún más simples. Esta extensión implementa estrictamente la interfaz de transporte JSON-RPC 2.0 y ofrece transporte mediante solicitudes HTTP GET, POST, y también websockets con Sisk. Puede instalar la extensión vía Nuget con el siguiente comando. Tenga en cuenta que, en versiones experimentales/beta, debe habilitar la opción de buscar paquetes prerelease en Visual Studio. ```bash dotnet add package Sisk.JsonRpc ``` ## Interfaz de Transporte JSON-RPC es un protocolo de llamada a procedimiento remoto (RPC) asíncrono y sin estado que utiliza JSON para la comunicación de datos. Una solicitud JSON-RPC se identifica típicamente por un ID, y una respuesta se entrega con el mismo ID que se envió en la solicitud. No todas las solicitudes requieren una respuesta, las cuales se denominan "notificaciones". La [especificación JSON-RPC 2.0](https://www.jsonrpc.org/specification) explica en detalle cómo funciona el transporte. Este transporte es agnóstico respecto a dónde se utilice. Sisk implementa este protocolo a través de HTTP, siguiendo las conformidades de [JSON-RPC over HTTP](https://www.jsonrpc.org/historical/json-rpc-over-http.html), que soporta parcialmente solicitudes GET, pero soporta completamente solicitudes POST. Los websockets también son compatibles, proporcionando comunicación de mensajes asíncrona. Una solicitud JSON-RPC se parece a: ```json { "jsonrpc": "2.0", "method": "Sum", "params": [1, 2, 4], "id": 1 } ``` Y una respuesta exitosa se parece a: ```json { "jsonrpc": "2.0", "result": 7, "id": 1 } ``` ## Métodos JSON-RPC El siguiente ejemplo muestra cómo crear una API JSON-RPC usando Sisk. Una clase de operaciones matemáticas realiza las operaciones remotas y entrega la respuesta serializada al cliente. ```csharp {title="Program.cs"} using var app = HttpServer.CreateBuilder(port: 5555) .UseJsonRPC((sender, args) => { // agrega todos los métodos etiquetados con WebMethod al manejador JSON-RPC args.Handler.Methods.AddMethodsFromType(new MathOperations()); // asigna la ruta /service para manejar solicitudes JSON-RPC POST y GET args.Router.MapPost("/service", args.Handler.Transport.HttpPost); args.Router.MapGet("/service", args.Handler.Transport.HttpGet); // asigna el transporte WebSocket JSON-RPC en 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); } } ``` El ejemplo anterior asignará los métodos `Sum` y `Sqrt` al manejador JSON-RPC, y estos métodos estarán disponibles en `GET /service`, `POST /service` y `GET /ws`. Los nombres de los métodos no distinguen entre mayúsculas y minúsculas. Los parámetros del método se deserializan automáticamente a sus tipos específicos. También se admite el uso de una solicitud con parámetros nombrados. La serialización JSON se realiza mediante la biblioteca [LightJson](https://github.com/CypherPotato/LightJson). Cuando un tipo no se deserializa correctamente, puede crear un [convertidor JSON](https://github.com/CypherPotato/LightJson?tab=readme-ov-file#json-converters) específico para ese tipo y asociarlo con [JsonRpcHandler.JsonSerializerOptions](https://docs.sisk-framework.org/api/Sisk.JsonRPC.JsonRpcHandler.JsonSerializerOptions.md). También puede obtener el objeto bruto `$.params` de la solicitud JSON-RPC directamente en su método. ```csharp {title="MathOperations.cs"} [WebMethod] public float Sum(JsonArray|JsonObject @params) { ... } ``` Para que esto ocurra, `@params` debe ser el **único** parámetro en su método, con exactamente el nombre `params` (en C#, el `@` es necesario para escapar este nombre de parámetro). La deserialización de parámetros ocurre tanto para objetos nombrados como para matrices posicionales. Por ejemplo, el siguiente método puede ser llamado remotamente por ambas solicitudes: ```csharp [WebMethod] public float AddUserToStore(string apiKey, User user, UserStore store) { ... } ``` Para una matriz, se debe seguir el orden de los parámetros. ```json { "jsonrpc": "2.0", "method": "AddUserToStore", "params": [ "1234567890", { "name": "John Doe", "email": "john@example.com" }, { "name": "My Store" } ], "id": 1 } ``` ## Personalizando el serializador Puede personalizar el serializador JSON en la propiedad [JsonRpcHandler.JsonSerializerOptions](https://docs.sisk-framework.org/api/Sisk.JsonRPC.JsonRpcHandler.JsonSerializerOptions.md). En esta propiedad, puede habilitar el uso de [JSON5](https://json5.org/) para deserializar mensajes. Aunque no es una conformidad con JSON-RPC 2.0, JSON5 es una extensión de JSON que permite una escritura más legible y fácil de entender. ```csharp {title="Program.cs"} using var host = HttpServer.CreateBuilder ( 5556 ) .UseJsonRPC ( ( o, e ) => { // usa un comparador de nombres sanitizado. este comparador compara solo letras // y dígitos en un nombre, y descarta otros símbolos. ej: // foo_bar10 == FooBar10 e.Handler.JsonSerializerOptions.PropertyNameComparer = new JsonSanitizedComparer ( ); // habilita JSON5 para el intérprete JSON. incluso activándolo, JSON plano sigue siendo permitido e.Handler.JsonSerializerOptions.SerializationFlags = LightJson.Serialization.JsonSerializationFlags.Json5; // asigna la ruta POST /service al manejador JSON RPC e.Router.MapPost ( "/service", e.Handler.Transport.HttpPost ); } ) .Build ( ); host.Start ( ); ``` --- # Proveedores de Servicios Source: https://docs.sisk-framework.org/es/docs/extensions/service-providers.html Los Proveedores de Servicios son una forma de portar tu aplicación Sisk a diferentes entornos con un archivo de configuración portable. Esta característica permite cambiar el puerto del servidor, parámetros y otras opciones sin tener que modificar el código de la aplicación para cada entorno. Este módulo depende de la sintaxis de construcción de Sisk y puede configurarse mediante el método **UsePortableConfiguration**. Un proveedor de configuración se implementa con **IConfigurationProvider**, que proporciona un lector de configuración y puede recibir cualquier implementación. Por defecto, Sisk ofrece un lector de configuración JSON, pero también existe un paquete para archivos INI. Puedes crear tu propio proveedor de configuración y registrarlo con: ```csharp using var app = HttpServer.CreateBuilder() .UsePortableConfiguration(config => { config.WithConfigReader(); }) .Build(); ``` Como se mencionó antes, el proveedor predeterminado es un archivo JSON. Por defecto, el nombre de archivo buscado es **service-config.json**, y se busca en el directorio actual del proceso en ejecución, no en el directorio del ejecutable. Puedes elegir cambiar el nombre del archivo, así como dónde Sisk debe buscar el archivo de configuración, con: ```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(); ``` El código anterior buscará el archivo **config.toml** en el directorio actual del proceso en ejecución. Si no se encuentra, buscará en el directorio donde está ubicado el ejecutable. Si el archivo no existe, se respeta el parámetro **createIfDontExists**, creando el archivo, sin contenido, en la última ruta probada (según **lookupDirectories**), y se lanza un error en la consola, impidiendo que la aplicación se inicialice. > [!TIP] > > Puedes consultar el código fuente del lector de configuración INI y del lector de configuración JSON para entender cómo se implementa un **IConfigurationProvider**. ## Lectura de configuraciones desde un archivo JSON Por defecto, Sisk proporciona un proveedor de configuración que lee configuraciones desde un archivo JSON. Este archivo sigue una estructura fija y está compuesto por los siguientes parámetros: ```json { "Server": { "DefaultEncoding": "UTF-8", "ThrowExceptions": true, "IncludeRequestIdHeader": true }, "ListeningHost": { "Label": "My sisk application", "Ports": [ "http://localhost:80/", "https://localhost:443/", // Los archivos de configuración también admiten comentarios ], "CrossOriginResourceSharingPolicy": { "AllowOrigin": "*", "AllowOrigins": [ "*" ], // nuevo en 0.14 "AllowMethods": [ "*" ], "AllowHeaders": [ "*" ], "MaxAge": 3600 }, "Parameters": { "MySqlConnection": "server=localhost;user=root;" } } } ``` Los parámetros creados a partir de un archivo de configuración pueden accederse en el constructor del servidor: ```csharp using var app = HttpServer.CreateBuilder() .UsePortableConfiguration(config => { config.WithParameters(paramCollection => { string databaseConnection = paramCollection.GetValueOrThrow("MySqlConnection"); }); }) .Build(); ``` Cada lector de configuración proporciona una forma de leer los parámetros de inicialización del servidor. Algunas propiedades se indican para estar en el entorno del proceso en lugar de definirse en el archivo de configuración, como datos sensibles de API, claves de API, etc. ## Estructura del archivo de configuración El archivo de configuración JSON está compuesto por las siguientes propiedades:
    Property Mandatory Description
    Server Required Representa el propio servidor con sus configuraciones.
    Server.AccessLogsStream Optional Por defecto console. Especifica la corriente de salida del registro de acceso. Puede ser un nombre de archivo, null o console.
    Server.ErrorsLogsStream Optional Por defecto null. Especifica la corriente de salida del registro de errores. Puede ser un nombre de archivo, null o console.
    Server.MaximumContentLength Optional
    Server.MaximumContentLength Optional Especifica la longitud máxima del contenido en bytes. Cero significa infinito. Cuando el objeto Server está presente y esta propiedad se omite, el valor es 0 (infinito), lo que sobrescribe el valor predeterminado de 100 MB de HttpServerConfiguration.MaximumContentLength.
    Server.IncludeRequestIdHeader Optional Por defecto false. Especifica si el servidor HTTP debe enviar el encabezado X-Request-Id.
    Server.ThrowExceptions Optional Por defecto true. Especifica si las excepciones no manejadas deben lanzarse. Establece false en producción y true durante la depuración.
    ListeningHost Required Representa el host de escucha del servidor.
    ListeningHost.Label Optional Representa la etiqueta de la aplicación.
    ListeningHost.Ports Required Representa un arreglo de cadenas, que coincide con la sintaxis de ListeningPort.
    ListeningHost.CrossOriginResourceSharingPolicy Optional Configura los encabezados CORS para la aplicación.
    ListeningHost.CrossOriginResourceSharingPolicy.AllowCredentials Optional Por defecto false. Especifica el encabezado Allow-Credentials.
    ListeningHost.CrossOriginResourceSharingPolicy.ExposeHeaders Optional Por defecto null. Esta propiedad espera un arreglo de cadenas. Especifica el encabezado Expose-Headers.
    ListeningHost.CrossOriginResourceSharingPolicy.AllowOrigin Optional Por defecto null. Esta propiedad espera una cadena. Especifica el encabezado Allow-Origin.
    ListeningHost.CrossOriginResourceSharingPolicy.AllowOrigins Optional Por defecto null. Esta propiedad espera un arreglo de cadenas. Especifica múltiples encabezados Allow-Origin. Consulta AllowOrigins para más información.
    ListeningHost.CrossOriginResourceSharingPolicy.AllowMethods Optional Por defecto null. Esta propiedad espera un arreglo de cadenas. Especifica el encabezado Allow-Methods.
    ListeningHost.CrossOriginResourceSharingPolicy.AllowHeaders Optional Por defecto null. Esta propiedad espera un arreglo de cadenas. Especifica el encabezado Allow-Headers.
    ListeningHost.CrossOriginResourceSharingPolicy.MaxAge Optional Por defecto null. Esta propiedad espera un entero. Especifica el encabezado Max-Age en segundos.
    ListeningHost.Parameters Optional Especifica las propiedades proporcionadas al método de configuración de la aplicación.
    --- # Proveedor de configuración INI Source: https://docs.sisk-framework.org/es/docs/extensions/ini-configuration.html Sisk tiene un método para obtener configuraciones de inicio distintas de JSON. De hecho, cualquier canal que implemente [IConfigurationReader](https://docs.sisk-framework.org/api/Sisk.Core.Http.Hosting.IConfigurationReader.md) puede usarse con [PortableConfigurationBuilder.WithConfigurationPipeline](https://docs.sisk-framework.org/api/Sisk.Core.Http.Hosting.PortableConfigurationBuilder.md), leyendo la configuración del servidor desde cualquier tipo de archivo. El paquete [Sisk.IniConfiguration](https://www.nuget.org/packages/Sisk.IniConfiguration/) ofrece un lector de archivos INI basado en streams que no lanza excepciones por errores de sintaxis comunes y posee una sintaxis de configuración sencilla. Este paquete puede usarse fuera del framework Sisk, ofreciendo flexibilidad para proyectos que requieran un lector de documentos INI eficiente. ## Instalación Para instalar el paquete, puedes comenzar con: ```bash $ dotnet add package Sisk.IniConfiguration ``` También puedes instalar el paquete central, que no incluye el INI [IConfigurationReader](https://docs.sisk-framework.org/api/Sisk.Core.Http.Hosting.IConfigurationReader), ni la dependencia de Sisk, solo los serializadores INI: ```bash $ dotnet add package Sisk.IniConfiguration.Core ``` Con el paquete principal, puedes usarlo en tu código como se muestra en el ejemplo a continuación: ```cs class Program { static HttpServerHostContext Host = null!; static void Main(string[] args) { Host = HttpServer.CreateBuilder() .UsePortableConfiguration(config => { config.WithConfigFile("app.ini", createIfDontExists: true); // usa el lector de configuración IniConfigurationReader 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}!"); } } ``` El código anterior buscará un archivo app.ini en el directorio actual del proceso (CurrentDirectory). El archivo INI se ve así: ```ini [Server] # Se admiten múltiples direcciones de escucha 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" ``` ## Sabor y sintaxis de INI Sabor de la implementación actual: - Los nombres de propiedades y secciones son **insensibles a mayúsculas/minúsculas**. - Los nombres y valores de las propiedades están **recortados**, a menos que los valores estén entre comillas. - Los valores pueden ir entre comillas simples o dobles. Las comillas pueden contener saltos de línea en su interior. - Se admiten comentarios con `#` y `;`. Además, **se permiten comentarios al final de la línea**. - Las propiedades pueden tener múltiples valores. En detalle, la documentación del “sabor” del analizador INI usado en Sisk está [disponible en este documento](https://github.com/sisk-http/archive/blob/master/ext/ini-reader-syntax.md). Usando el siguiente código INI como ejemplo: ```ini One = 1 Value = this is an value Another value = "this value has an line break on it" ; el código a continuación tiene algunos colores [some section] Color = Red Color = Blue Color = Yellow ; no usar amarillo ``` Analízalo con: ```csharp // analiza el texto INI desde la cadena IniDocument doc = IniDocument.FromString(iniText); // obtener un valor string? one = doc.Global.GetOne("one"); string? anotherValue = doc.Global.GetOne("another value"); // obtener múltiples valores string[]? colors = doc.GetSection("some section")?.GetMany("color"); ``` ## Parámetros de configuración | Sección y nombre | Permitir múltiples valores | Descripción | | ---------------- | -------------------------- | ----------- | | `Server.Listen` | Yes | Direcciones/puertos de escucha del servidor. | | `Server.Encoding` | No | Codificación predeterminada del servidor. | | `Server.MaximumContentLength` | No | Tamaño máximo de content-length del servidor en bytes. Cuando se omite, se mantiene la codificación predeterminada del servidor (100 MB desde Sisk 1.7). Usa `0` para sin límite. | | `Server.IncludeRequestIdHeader` | No | Especifica si el servidor HTTP debe enviar el encabezado X-Request-Id. | | `Server.ThrowExceptions` | No | Especifica si se deben lanzar excepciones no controladas. | | `Server.AccessLogsStream` | No | Especifica el stream de salida del registro de accesos. | | `Server.ErrorsLogsStream` | No | Especifica el stream de salida del registro de errores. | | `Cors.AllowMethods` | No | Especifica el valor del encabezado CORS Allow-Methods. | | `Cors.AllowHeaders` | No | Especifica el valor del encabezado CORS Allow-Headers. | | `Cors.AllowOrigins` | No | Especifica múltiples encabezados Allow-Origin, separados por comas. [AllowOrigins](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AllowOrigins.md) para más información. | | `Cors.AllowOrigin` | No | Especifica un encabezado Allow-Origin. | | `Cors.ExposeHeaders` | No | Especifica el valor del encabezado CORS Expose-Headers. | | `Cors.AllowCredentials` | No | Especifica el valor del encabezado CORS Allow-Credentials. | | `Cors.MaxAge` | No | Especifica el valor del encabezado CORS Max-Age. | --- # Documentación de API Source: https://docs.sisk-framework.org/es/docs/extensions/api-documentation.html La extensión `Sisk.Documenting` le permite generar documentación de API para su aplicación Sisk de forma automática. Aprovecha la estructura de su código y los atributos para crear un sitio de documentación completo, con soporte para exportar al formato Open API (Swagger). > [!WARNING] > This package is published as a prerelease (1.7.0-rc3). Its behavior and API may be subject to change in future updates. Instale el paquete [Sisk.Documenting](https://www.nuget.org/packages/Sisk.Documenting/) desde NuGet. Para servir la documentación como una página HTML, también instale [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 ``` El código fuente está disponible [aquí](https://github.com/sisk-http/core/tree/main/extensions/Sisk.Documenting). Para usar `Sisk.Documenting`, necesita registrarlo en el constructor de su aplicación y decorar sus manejadores de rutas con atributos de documentación. ### Registro de generación de documentación Utilice el método de extensión `UseApiDocumentation` en su `HttpServerHostContextBuilder` para exponer la documentación de API generada desde el mismo router que sirve su aplicación. ```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**: Define los metadatos de su aplicación, como nombre, descripción y versión. - **routerPath**: La ruta URL donde la interfaz de usuario de la documentación (o JSON) será accesible. - **exporter**: Configura cómo se exporta la documentación. El `OpenApiExporter` habilita el soporte Open API (Swagger). ### Documentación de Endpoints Puede describir sus endpoints usando los atributos `[ApiEndpoint]` y `[ApiQueryParameter]` en los métodos manejadores de rutas. ### `ApiEndpoint` El atributo `[ApiEndpoint]` le permite proporcionar una descripción para el endpoint. ```csharp [ApiEndpoint(Description = "Returns a greeting message.")] public HttpResponse Index(HttpRequest request) { ... } ``` ### `ApiQueryParameter` El atributo `[ApiQueryParameter]` documenta los parámetros de cadena de consulta que acepta el endpoint. ```csharp [ApiQueryParameter(name: "name", IsRequired = false, Description = "The name of the person to greet.", Type = "string")] public HttpResponse Index(HttpRequest request) { ... } ``` - **name**: El nombre del parámetro de consulta. - **IsRequired**: Especifica si el parámetro es obligatorio. - **Description**: Una descripción legible del parámetro. - **Type**: El tipo de dato esperado (p. ej., "string", "int"). ### `ApiEndpoint` Anota un endpoint con información general. * **Name** (string, required in constructor): El nombre del endpoint API. * **Description** (string): Una breve descripción de lo que hace el endpoint. * **Group** (string): Permite agrupar endpoints (p. ej., por controlador o módulo). * **InheritDescriptionFromXmlDocumentation** (bool, default: `true`): Si es `true`, intenta usar el resumen de la documentación XML del método si `Description` no está establecido. ### `ApiHeader` Documenta un encabezado HTTP específico que el endpoint espera o utiliza. * **HeaderName** (string, required in constructor): La clave del encabezado (p. ej., "Authorization"). * **Description** (string): Describe el propósito del encabezado. * **IsRequired** (bool): Indica si el encabezado es obligatorio para la solicitud. ### `ApiParameter` Define un parámetro genérico para el endpoint, a menudo usado para campos de formulario o parámetros de cuerpo que no están cubiertos por otros atributos. * **Name** (string, required in constructor): El nombre del parámetro. * **TypeName** (string, required in constructor): El tipo de dato del parámetro (p. ej., "string", "int"). * **Description** (string): Una descripción del parámetro. * **IsRequired** (bool): Indica si el parámetro es obligatorio. ### `ApiParameterExample` Agrega un valor de ejemplo para un parámetro documentado en una situación particular. El atributo puede aplicarse múltiples veces al mismo método. * **ParameterName** (string, required in constructor): El nombre del parámetro al que se aplica el ejemplo. * **Title** (string, required in constructor): Un título que identifica la situación ilustrada por el ejemplo. * **Description** (string): Cuándo o por qué se aplica el ejemplo. * **Example** (string): El valor de ejemplo. * **ExampleLanguage** (string): El lenguaje usado para formatear el valor de ejemplo, si corresponde. ```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` Genera automáticamente la documentación de parámetros a partir de las propiedades de una clase o tipo especificado. * **Type** (Type, required in constructor): El `Type` de la clase del que reflejar las propiedades. ### `ApiPathParameter` Documenta una variable de ruta (p. ej., en `/users/{id}`). * **Name** (string, required in constructor): El nombre del parámetro de ruta. * **Description** (string): Describe lo que representa el parámetro. * **Type** (string): El tipo de dato esperado. ### `ApiQueryParameter` Documenta un parámetro de cadena de consulta (p. ej., `?page=1`). * **Name** (string, required in constructor): La clave del parámetro de consulta. * **Description** (string): Describe el parámetro. * **Type** (string): El tipo de dato esperado. * **IsRequired** (bool): Indica si el parámetro de consulta debe estar presente. ### `ApiRequest` Describe el cuerpo de la solicitud esperado. * **Description** (string, required in constructor): Una descripción del cuerpo de la solicitud. * **Example** (string): Una cadena cruda que contiene un ejemplo del cuerpo de la solicitud. * **ExampleLanguage** (string): El lenguaje del ejemplo (p. ej., "json", "xml"). * **PayloadType** (Type): Si se establece, el ejemplo y el esquema se generarán automáticamente a partir de este tipo cuando los manejadores de contexto configurados lo soporten. ### `ApiResponse` Describe una posible respuesta del endpoint. * **StatusCode** (HttpStatusCode, required in constructor): El código de estado HTTP devuelto (p. ej., `HttpStatusCode.OK`). * **Description** (string): Describe la condición para esta respuesta. * **Example** (string): Una cadena cruda que contiene un ejemplo del cuerpo de la respuesta. * **ExampleLanguage** (string): El lenguaje del ejemplo. * **PayloadType** (Type): Si se establece, el ejemplo y el esquema se generarán automáticamente a partir de este tipo cuando los manejadores de contexto configurados lo soporten. ## Manejadores de Tipo Los manejadores de tipo son responsables de convertir sus tipos .NET (clases, enumeraciones, etc.) en ejemplos de documentación. Esto es particularmente útil para generar automáticamente ejemplos de cuerpos de solicitud y respuesta basados en sus modelos de datos. Estos manejadores se configuran dentro del `ApiGenerationContext`. ```csharp using Sisk.Documenting.Content; var context = new ApiGenerationContext() { // ... BodyExampleTypeHandler = new JsonContentTypeHandler(), ParameterExampleTypeHandler = new JsonContentTypeHandler(), ContentSchemaTypeHandler = new JsonContentTypeHandler() }; ``` ### JsonContentTypeHandler El `JsonContentTypeHandler` es un manejador incorporado que genera ejemplos JSON, ejemplos de parámetros y esquemas JSON. Implementa `IExampleBodyTypeHandler`, `IExampleParameterTypeHandler` y `IContentSchemaTypeHandler`. Puede personalizarse con opciones específicas de `JsonSerializerOptions` o `IJsonTypeInfoResolver` para que coincidan con la lógica de serialización de su aplicación. ```csharp var jsonHandler = new JsonContentTypeHandler(new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, WriteIndented = true }); context.BodyExampleTypeHandler = jsonHandler; context.ParameterExampleTypeHandler = jsonHandler; context.ContentSchemaTypeHandler = jsonHandler; ``` ### Manejadores de Tipo Personalizados Puede implementar sus propios manejadores para soportar otros formatos (como XML) o para personalizar cómo se generan los ejemplos. #### IExampleBodyTypeHandler Implemente esta interfaz para generar ejemplos de cuerpo para tipos de solicitud y respuesta. ```csharp public class XmlExampleTypeHandler : IExampleBodyTypeHandler { public BodyExampleResult? GetBodyExampleForType(Type type) { // Generar cadena XML para el tipo string xmlContent = MyXmlGenerator.Generate(type); return new BodyExampleResult(xmlContent, "xml"); } } ``` #### IExampleParameterTypeHandler Implemente esta interfaz para generar descripciones detalladas de parámetros a partir de un tipo (usado por `[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(); } } ``` ## Exportadores Los exportadores son responsables de convertir los metadatos de documentación de API recopilados en un formato específico que pueda ser consumido por otras herramientas o mostrado al usuario. ### OpenApiExporter El exportador predeterminado es el `OpenApiExporter`, que genera un archivo JSON siguiendo la [OpenAPI Specification 3.0.0](https://spec.openapis.org/oas/v3.0.0). ```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 El `LlmsApiExporter` genera un documento de texto plano en el estilo [llms.txt](https://llmstxt.org/), destinado a ser leído por modelos de lenguaje. Puede establecer el `Hostname` usado en los ejemplos, y un `HeaderText` y un `FooterText` que se escriben al principio y al final del documento: ```csharp new LlmsApiExporter( hostname: "https://api.example.com", headerText: "Public API of the Example service.", footerText: "Contact support@example.com for an API key.") ``` ### Creación de un Exportador Personalizado Puede crear su propio exportador implementando la interfaz `IApiDocumentationExporter`. Esto le permite generar documentación en formatos como Markdown, HTML, Postman Collection o cualquier otro formato personalizado. La interfaz requiere que implemente un único método: `ExportDocumentationContent`. ```csharp using Sisk.Core.Http; using Sisk.Documenting; public class MyCustomExporter : IApiDocumentationExporter { public HttpContent ExportDocumentationContent(ApiDocumentation documentation) { // 1. Procesar el objeto de documentación 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. Devolver el contenido como HttpContent return new StringContent(sb.ToString(), Encoding.UTF8, "text/markdown"); } } ``` Luego, simplemente úselo en su configuración: ```csharp host.UseApiDocumentation( // ... exporter: new MyCustomExporter() ); ``` ### Ejemplo Completo A continuación se muestra un ejemplo completo que demuestra cómo configurar `Sisk.Documenting` y documentar un controlador sencillo. ```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}!"); } } ``` En este ejemplo, acceder a `/api/docs` servirá la documentación generada para la API "My application", describiendo el endpoint `GET /` y su parámetro `name`. --- # Autenticación Básica (descontinuada) Source: https://docs.sisk-framework.org/es/docs/extensions/basic-auth.html > [!CAUTION] > **Descontinuado, no lo use.** El paquete Sisk.BasicAuth ya no se mantiene y está obsoleto en NuGet, sin paquete de reemplazo. Su `BasicAuthenticateRequestHandler` no decodifica las credenciales Base64 enviadas por clientes HTTP estándar, por lo que las solicitudes autenticadas son rechazadas. Implemente la autenticación básica con su propio [request handler](https://docs.sisk-framework.org/es/docs/fundamentals/request-handlers.md) que lea [HttpHeaderCollection.Authorization](https://docs.sisk-framework.org/api/Sisk.Core.Entity.HttpHeaderCollection.Authorization.md) en su lugar. Esta página se conserva solo como referencia histórica. El paquete Basic Auth agrega un manejador de solicitudes capaz de manejar el esquema de autenticación básica en su aplicación Sisk con muy poca configuración y esfuerzo. La autenticación HTTP básica es una forma mínima de autenticación de solicitudes mediante un ID de usuario y una contraseña, donde la sesión es controlada exclusivamente por el cliente y no existen tokens de autenticación o acceso. ![Basic Auth](https://docs.sisk-framework.org/assets/img/basic-auth.svg) Obtenga más información sobre el esquema de autenticación básica en la [especificación MDN](https://developer.mozilla.org/pt-BR/docs/Web/HTTP/Authentication). ## Instalación Para comenzar, instale el paquete Sisk.BasicAuth en su proyecto: > dotnet add package Sisk.BasicAuth Puede ver más formas de instalarlo en su proyecto en el [repositorio NuGet](https://www.nuget.org/packages/Sisk.BasicAuth/0.15.0). ## Creando su manejador de autenticación Puede controlar el esquema de autenticación para un módulo completo o para rutas individuales. Para ello, escribamos primero nuestro primer manejador de autenticación básica. ```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(); // en este caso, estamos usando el correo electrónico como campo de ID de usuario, así que // vamos a buscar un usuario usando su correo electrónico. User? user = db.Users.FirstOrDefault(u => u.Email == credentials.UserId); if (user == null) { return base.CreateUnauthorizedResponse("Sorry! No user was found by this email."); } // valida que la contraseña de las credenciales sea válida para este usuario. if (!user.ValidatePassword(credentials.Password)) { return base.CreateUnauthorizedResponse("Invalid credentials."); } // agrega el usuario autenticado al contexto http // y continúa la ejecución context.Bag.Add("loggedUser", user); return null; } } ``` Entonces, simplemente asocie este manejador de solicitudes con nuestra ruta o clase. ```cs public class UsersController { [RouteGet("/")] [RequestHandler(typeof(UserAuthHandler))] public string Index(HttpRequest request) { User loggedUser = request.Bag.Get(); return $"Hello, {loggedUser.Name}!"; } } ``` O usando la clase [RouterModule](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouterModule.md): ```cs public class UsersController : RouterModule { public ClientModule() { // todas las rutas dentro de esta clase serán manejadas por // UserAuthHandler. base.HasRequestHandler(new UserAuthHandler()); } [RouteGet("/")] public string Index(HttpRequest request) { User loggedUser = request.Bag.Get(); return $"Hello, {loggedUser.Name}!"; } } ``` ## Comentarios La responsabilidad principal de la autenticación básica se lleva a cabo en el lado del cliente. El almacenamiento, el control de caché y el cifrado se manejan localmente en el cliente. El servidor solo recibe las credenciales y valida si se permite o no el acceso. Observe que este método no es de los más seguros porque coloca una responsabilidad significativa en el cliente, lo que puede dificultar rastrear y mantener la seguridad de sus credenciales. Además, es crucial que las contraseñas se transmitan en un contexto de conexión segura (SSL), ya que no poseen cifrado propio. Una breve interceptación en los encabezados de una solicitud puede exponer las credenciales de acceso de su usuario. Opte por soluciones de autenticación más robustas para aplicaciones en producción y evite usar demasiados componentes prefabricados, ya que pueden no adaptarse a las necesidades de su proyecto y terminar exponiéndolo a riesgos de seguridad. --- # Proxy SSL (descontinuado) Source: https://docs.sisk-framework.org/es/docs/extensions/ssl-proxy.html > [!CAUTION] > **Descontinuado, no usar.** El paquete Sisk.SslProxy ya no se mantiene y está obsoleto en NuGet, sin paquete de reemplazo. Ya no compila contra la API actual de Cadente. Para servir HTTPS, use el soporte SSL nativo del [motor Cadente](https://docs.sisk-framework.org/es/docs/cadente.md) o una de las opciones en [Trabajando con SSL](https://docs.sisk-framework.org/es/docs/ssl.md). Esta página se conserva solo como referencia histórica. El Proxy SSL de Sisk es un módulo que proporciona una conexión HTTPS para un [ListeningHost](https://docs.sisk-framework.org/api/Sisk.Core.Http.ListeningHost.md) en Sisk y enruta los mensajes HTTPS a un contexto HTTP inseguro. El módulo se construyó para ofrecer conexión SSL a un servicio que utiliza [HttpListener](https://learn.microsoft.com/pt-br/dotnet/api/system.net.httplistener?view=net-8.0) para ejecutarse, el cual no soporta SSL. El proxy se ejecuta dentro de la misma aplicación y escucha mensajes HTTP/1.1, reenviándolos en el mismo protocolo a Sisk. Actualmente, esta característica es altamente experimental y puede ser lo suficientemente inestable como para no usarse en producción. En la actualidad, SslProxy soporta casi todas las características de HTTP/1.1, como keep-alive, codificación chunked, websockets, etc. Para una conexión abierta al proxy SSL, se crea una conexión TCP al servidor de destino, y el proxy se reenvía a la conexión establecida. SslProxy puede usarse con HttpServer.CreateBuilder de la siguiente manera: ```csharp using var app = HttpServer.CreateBuilder(port: 5555) .UseRouter(r => { r.MapGet("/", request => { return new HttpResponse("Hello, world!"); }); }) // agregar SSL al proyecto .UseSsl( sslListeningPort: 5567, new X509Certificate2(@".\ssl.pfx", password: "12345") ) .Build(); app.Start(); ``` Debe proporcionar un certificado SSL válido para el proxy. Para asegurarse de que el certificado sea aceptado por los navegadores, recuerde importarlo en el sistema operativo para que funcione correctamente. --- # Configuración manual (avanzada) Source: https://docs.sisk-framework.org/es/docs/advanced/manual-setup.html Utilice la configuración manual cuando necesite ensamblar los componentes del servidor usted mismo, por ejemplo cuando un proceso debe exponer varios hosts, puertos, routers o una configuración de servidor personalizada. Para la mayoría de las aplicaciones, la API del constructor es más corta y debe preferirse. La configuración manual es útil cuando desea un control directo sobre los cuatro componentes principales: un `Router`, uno o más objetos `ListeningHost`, una `HttpServerConfiguration` y el `HttpServer` final. Primero, debemos entender el concepto de solicitud/respuesta. Es bastante simple: por cada solicitud debe haber una respuesta. Sisk también sigue este principio. Creemos un método que responda con un mensaje "Hello, World!" en HTML, especificando el código de estado y los encabezados. ```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; } ``` El siguiente paso es asociar este método con una ruta HTTP. ## Enrutadores Los enrutadores son abstracciones de rutas de solicitud y sirven como puente entre las solicitudes y respuestas del servicio. Los enrutadores gestionan rutas del servicio, funciones y errores. Un enrutador puede tener varias rutas, y cada ruta puede realizar diferentes operaciones en esa ruta, como ejecutar una función, servir una página o proporcionar un recurso del servidor. Creemos nuestro primer enrutador y asociemos nuestro método `IndexPage` con la ruta de índice. ```csharp Router mainRouter = new Router(); mainRouter.MapGet("/", IndexPage); ``` Ahora nuestro enrutador puede recibir solicitudes y enviar respuestas. Sin embargo, `mainRouter` no está vinculado a un host o a un servidor, por lo que no funcionará por sí solo. El siguiente paso es crear nuestro ListeningHost. ## Hosts de escucha y puertos Un [ListeningHost](https://docs.sisk-framework.org/api/Sisk.Core.Http.ListeningHost.md) puede alojar un enrutador y varios puertos de escucha para el mismo enrutador. Un [ListeningPort](https://docs.sisk-framework.org/api/Sisk.Core.Http.ListeningPort.md) es un prefijo donde el servidor HTTP escuchará. Aquí, podemos crear un `ListeningHost` que apunte a dos puntos finales para nuestro enrutador: ```csharp ListeningHost myHost = new ListeningHost { Router = mainRouter, Ports = new ListeningPort[] { new ListeningPort("http://localhost:5000/") } }; ``` Ahora nuestro servidor HTTP escuchará los puntos finales especificados y redirigirá sus solicitudes a nuestro enrutador. ## Configuración del servidor La configuración del servidor es responsable de la mayor parte del comportamiento del propio servidor HTTP. En esta configuración, podemos asociar `ListeningHosts` con nuestro servidor. ```csharp HttpServerConfiguration config = new HttpServerConfiguration(); config.ListeningHosts.Add(myHost); // Add our ListeningHost to this server configuration ``` Opciones comunes de configuración del servidor: | Propiedad | Predeterminado | Cuándo usar | Notas | | --- | --- | --- | --- | | [RemoteRequestsAction](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.RemoteRequestsAction.md) | `RequestListenAction.Accept` | El servicio debe rechazar solicitudes no locales a menos que provengan de un proxy inverso de confianza. | Establézcalo en `Drop` solo cuando la topología de despliegue sea clara. | | [IncludeRequestIdHeader](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.IncludeRequestIdHeader.md) | `false` | Los clientes o proxies necesitan el id de solicitud de Sisk en el encabezado de respuesta `X-Request-Id`. | Combínelo con registros que incluyan `HttpRequest.RequestId`. | | [IdleConnectionTimeout](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.IdleConnectionTimeout.md) | `120` seconds | Las conexiones keep-alive inactivas deben cerrarse tarde o temprano. | Esto lo aplica el motor HTTP. | | [NormalizeHeadersEncodings](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.NormalizeHeadersEncodings.md) | `false` | Recibe encabezados con una discordancia de codificación. | Esto tiene un costo de procesamiento; déjelo deshabilitado a menos que sea necesario. | | [SendSiskHeader](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.SendSiskHeader.md) | `true` | Desea ocultar o exponer el encabezado `X-Powered-By` de Sisk. | Desactívelo para políticas de encabezados de producción más estrictas. | | [OptionsLogMode](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.OptionsLogMode.md) | `LogOutput.Both` | Desea reducir o redirigir los registros generados por el manejo automático de `OPTIONS`. | Utiliza los mismos valores de modo de registro que las rutas. | | [AsyncRequestProcessing](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.AsyncRequestProcessing.md) | `true` | Necesita procesamiento determinista de una sola solicitud para diagnóstico. | Desactivarlo limita el rendimiento. | | [DisposeDisposableContextValues](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.DisposeDisposableContextValues.md) | `true` | Los valores del contenedor de solicitud que implementan `IDisposable` deben disponerse automáticamente. | Manténgalo habilitado a menos que la propiedad se gestione en otro lugar. | | [ConvertIAsyncEnumerableIntoEnumerable](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ConvertIAsyncEnumerableIntoEnumerable.md) | `true` | Los manejadores de valores deben recibir enumerables asíncronos como valores enumerables bloqueantes. | Desactívelo cuando implemente su propio manejo de flujos asíncronos. | | [KeepAlive](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.KeepAlive.md) | `true` | Las conexiones deben permanecer reutilizables después de las respuestas. | Desactívelo para clientes o intermediarios que no manejan bien las conexiones persistentes. | | [ForceTrailingSlash](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ForceTrailingSlash.md) | `false` | Las rutas GET deben redirigir a una URL con barra diagonal final. | Se aplica solo a rutas que no son expresiones regulares. | | [MaximumContentLength](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.MaximumContentLength.md) | `100 MB` | Los cuerpos de solicitud o mensajes WebSocket necesitan un límite de tamaño diferente. | El valor predeterminado era `0` (ilimitado) antes de Sisk 1.7. Establézcalo en `0` para eliminar el límite. | | [EnableAutomaticResponseCompression](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.EnableAutomaticResponseCompression.md) | `false` | Las respuestas deben comprimirse automáticamente cuando el cliente lo soporta. | Las respuestas `CompressedContent` existentes no se comprimen de nuevo. | Ahora, podemos crear nuestro servidor HTTP: ```csharp HttpServer server = new HttpServer(config); server.Start(); // Starts the server Console.ReadKey(); // Prevents the application from exiting ``` Ahora podemos compilar nuestro ejecutable y ejecutar nuestro servidor HTTP con el comando: ```bash dotnet watch ``` En tiempo de ejecución, abra su navegador y navegue a la ruta del servidor, y debería ver: --- # Ciclo de vida de la solicitud Source: https://docs.sisk-framework.org/es/docs/advanced/request-lifecycle.html A continuación se explica todo el ciclo de vida de una solicitud mediante un ejemplo de una petición HTTP. - **Recepción de la solicitud:** cada solicitud crea un contexto HTTP entre la propia solicitud y la respuesta que se entregará al cliente. Este contexto proviene del listener incorporado en Sisk, que puede ser el [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) o [Cadente](https://blog.sisk-framework.org/posts/2025-01-29-cadente-experiment/). - Validación de solicitud externa: la validación de [HttpServerConfiguration.RemoteRequestsAction](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.RemoteRequestsAction.md) se valida para la solicitud. - Si la solicitud es externa y la propiedad es `Drop`, la conexión se cierra sin respuesta al cliente con un `HttpServerExecutionStatus = RemoteRequestDropped`. - Configuración del Resolutor de Reenvío: si se configura un [ForwardingResolver](https://docs.sisk-framework.org/es/docs/advanced/forwarding-resolvers.md), se llamará al método [OnResolveRequestHost](https://docs.sisk-framework.org/api/Sisk.Core.Http.ForwardingResolver.OnResolveRequestHost.md) en el host original de la solicitud. - Coincidencia DNS: con el host resuelto y con más de un [ListeningHost](https://docs.sisk-framework.org/api/Sisk.Core.Http.ListeningHost.md) configurado, el servidor buscará el host correspondiente para la solicitud. - Si no coincide ningún ListeningHost, se devuelve al cliente una respuesta 400 Bad Request y al contexto HTTP se le asigna el estado `HttpServerExecutionStatus = DnsUnknownHost`. - Si coincide un ListeningHost, pero su [Router](https://docs.sisk-framework.org/api/Sisk.Core.Http.ListeningHost.Router.md) aún no está inicializado, se devuelve al cliente una respuesta 503 Service Unavailable y al contexto HTTP se le asigna el estado `HttpServerExecutionStatus = ListeningHostNotReady`. - Vinculación del enrutador: el router del ListeningHost correspondiente se asocia con el servidor HTTP recibido. - Si el router ya está asociado a otro servidor HTTP, lo cual no está permitido porque el router utiliza activamente los recursos de configuración del servidor, se lanza una `InvalidOperationException`. Esto solo ocurre durante la inicialización del servidor HTTP, no durante la creación del contexto HTTP. - Predefinición de encabezados: - Predefine el encabezado `X-Request-Id` en la respuesta si está configurado para hacerlo. - Predefine el encabezado `X-Powered-By` en la respuesta si está configurado para hacerlo. - Validación del tamaño del contenido: valida si el contenido de la solicitud es menor que [HttpServerConfiguration.MaximumContentLength](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.MaximumContentLength.md) (100 MB por defecto) solo si es mayor que cero. - Si la solicitud envía un `Content-Length` mayor que el configurado, se devuelve al cliente una respuesta 413 Payload Too Large y al contexto HTTP se le asigna el estado `HttpServerExecutionStatus = ContentTooLarge`. - El evento `OnHttpRequestOpen` se invoca para todos los manejadores de servidor HTTP configurados. - **Enrutamiento de la acción:** el servidor invoca el router para la solicitud recibida. - Si el router no encuentra una ruta que coincida con la solicitud: - Si la propiedad [Router.NotFoundErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.NotFoundErrorHandler.md) está configurada, se invoca la acción y la respuesta de la acción se reenvía al cliente HTTP. - Si la propiedad anterior es nula, se devuelve al cliente una respuesta predeterminada 404 Not Found. - Si el router encuentra una ruta coincidente, pero el método de la ruta no coincide con el método de la solicitud: - Si la propiedad [Router.MethodNotAllowedErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.MethodNotAllowedErrorHandler.md) está configurada, se invoca la acción y la respuesta de la acción se reenvía al cliente HTTP. - Si la propiedad anterior es nula, se devuelve al cliente una respuesta predeterminada 405 Method Not Allowed. - Si la solicitud es del método `OPTIONS`: - El router devuelve una respuesta 200 Ok al cliente solo si ninguna ruta coincide con el método de la solicitud (el método de la ruta no está explícitamente definido como [RouteMethod.Options](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouteMethod.md)). - Si la propiedad [HttpServerConfiguration.ForceTrailingSlash](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ForceTrailingSlash.md) está habilitada, la ruta coincidente no es una expresión regular, la ruta de la solicitud no termina con `/` y el método de la solicitud es `GET`: - Se devuelve al cliente una respuesta HTTP 307 Temporary Redirect con el encabezado `Location` que contiene la ruta y la cadena de consulta apuntando a la misma ubicación pero con un `/` al final. - El evento `OnContextBagCreated` se invoca para todos los manejadores de servidor HTTP configurados. - Todas las instancias globales de [IRequestHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.IRequestHandler.md) con la bandera `BeforeResponse` se ejecutan. - Si algún manejador devuelve una respuesta no nula, esa respuesta se reenvía al cliente HTTP y el contexto se cierra. - Si se lanza un error en este paso y [HttpServerConfiguration.ThrowExceptions](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ThrowExceptions.md) está deshabilitado: - Si la propiedad [Router.CallbackErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.CallbackErrorHandler.md) está habilitada, se invoca y la respuesta resultante se devuelve al cliente. - Si la propiedad anterior no está definida, se devuelve al servidor una respuesta vacía, que a su vez reenvía una respuesta según el tipo de excepción lanzada, normalmente 500 Internal Server Error. - Todas las instancias de [IRequestHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.IRequestHandler.md) definidas en la ruta y con la bandera `BeforeResponse` se ejecutan. - Si algún manejador devuelve una respuesta no nula, esa respuesta se reenvía al cliente HTTP y el contexto se cierra. - Si se lanza un error en este paso y [HttpServerConfiguration.ThrowExceptions](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ThrowExceptions.md) está deshabilitado: - Si la propiedad [Router.CallbackErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.CallbackErrorHandler.md) está habilitada, se invoca y la respuesta resultante se devuelve al cliente. - Si la propiedad anterior no está definida, se devuelve al servidor una respuesta vacía, que a su vez reenvía una respuesta según el tipo de excepción lanzada, normalmente 500 Internal Server Error. - Se invoca la acción del router y se transforma en una respuesta HTTP. - Si se lanza un error en este paso y [HttpServerConfiguration.ThrowExceptions](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ThrowExceptions.md) está deshabilitado: - Si la propiedad [Router.CallbackErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.CallbackErrorHandler.md) está habilitada, se invoca y la respuesta resultante se devuelve al cliente. - Si la propiedad anterior no está definida, se devuelve al servidor una respuesta vacía, que a su vez reenvía una respuesta según el tipo de excepción lanzada, normalmente 500 Internal Server Error. - Todas las instancias globales de [IRequestHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.IRequestHandler.md) con la bandera `AfterResponse` se ejecutan. - Si algún manejador devuelve una respuesta no nula, la respuesta del manejador reemplaza la respuesta anterior y se reenvía inmediatamente al cliente HTTP. - Si se lanza un error en este paso y [HttpServerConfiguration.ThrowExceptions](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ThrowExceptions.md) está deshabilitado: - Si la propiedad [Router.CallbackErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.CallbackErrorHandler.md) está habilitada, se invoca y la respuesta resultante se devuelve al cliente. - Si la propiedad anterior no está definida, se devuelve al servidor una respuesta vacía, que a su vez reenvía una respuesta según el tipo de excepción lanzada, normalmente 500 Internal Server Error. - Todas las instancias de [IRequestHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.IRequestHandler.md) definidas en la ruta y con la bandera `AfterResponse` se ejecutan. - Si algún manejador devuelve una respuesta no nula, la respuesta del manejador reemplaza la respuesta anterior y se reenvía inmediatamente al cliente HTTP. - Si se lanza un error en este paso y [HttpServerConfiguration.ThrowExceptions](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ThrowExceptions.md) está deshabilitado: - Si la propiedad [Router.CallbackErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.CallbackErrorHandler.md) está habilitada, se invoca y la respuesta resultante se devuelve al cliente. - Si la propiedad anterior no está definida, se devuelve al servidor una respuesta vacía, que a su vez reenvía una respuesta según el tipo de excepción lanzada, normalmente 500 Internal Server Error. - **Procesamiento de la respuesta:** con la respuesta lista, el servidor la prepara para enviarla al cliente. - Los encabezados de la política Cross-Origin Resource Sharing (CORS) se definen en la respuesta según lo configurado en la actual [ListeningHost.CrossOriginResourceSharingPolicy](https://docs.sisk-framework.org/api/Sisk.Core.Http.ListeningHost.CrossOriginResourceSharingPolicy.md). - El código de estado y los encabezados de la respuesta se envían al cliente. - El contenido de la respuesta se envía al cliente: - Si el contenido de la respuesta es una subclase de [ByteArrayContent](https://learn.microsoft.com/en-us/dotnet/api/system.net.http.bytearraycontent), los bytes de la respuesta se copian directamente al flujo de salida de la respuesta. - Si la condición anterior no se cumple, la respuesta se serializa a un flujo y se copia al flujo de salida de la respuesta. - Los flujos se cierran y el contenido de la respuesta se descarta. - Si [HttpServerConfiguration.DisposeDisposableContextValues](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.DisposeDisposableContextValues.md) está habilitado, todos los objetos definidos en el contexto de la solicitud que heredan de [IDisposable](https://learn.microsoft.com/en-us/dotnet/api/system.idisposable) se descartan. - El evento `OnHttpRequestClose` se invoca para todos los manejadores de servidor HTTP configurados. - Si se lanzó una excepción en el servidor, el evento `OnException` se invoca para todos los manejadores de servidor HTTP configurados. - Si la ruta permite el registro de accesos y [HttpServerConfiguration.AccessLogsStream](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.AccessLogsStream.md) no es nulo, se escribe una línea de registro en la salida de logs. - Si la ruta permite el registro de errores, hay una excepción y [HttpServerConfiguration.ErrorsLogsStream](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ErrorsLogsStream.md) no es nulo, se escribe una línea de registro en la salida de logs de errores. - Si el servidor está esperando una solicitud a través de [HttpServer.WaitNext](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServer.WaitNext.md), el mutex se libera y el contexto queda disponible para el usuario. --- # Resolvedores de Reenvío Source: https://docs.sisk-framework.org/es/docs/advanced/forwarding-resolvers.html Un Resolvedor de Reenvío es un ayudante que ayuda a decodificar información que identifica al cliente a través de una solicitud, proxy, CDN o balanceadores de carga. Cuando su servicio Sisk se ejecuta a través de un proxy inverso o directo, la dirección IP del cliente, el host y el protocolo pueden ser diferentes de la solicitud original ya que es un reenvío de un servicio a otro. Esta funcionalidad de Sisk le permite controlar y resolver esta información antes de trabajar con la solicitud. Estos proxies usualmente proporcionan encabezados útiles para identificar a su cliente. Actualmente, con la clase [ForwardingResolver](https://docs.sisk-framework.org/api/Sisk.Core.Http.ForwardingResolver.md) es posible resolver la dirección IP del cliente, el host y el protocolo HTTP utilizado. Después de la versión 1.0 de Sisk, el servidor ya no tiene una implementación estándar para decodificar estos encabezados por razones de seguridad que varían de servicio a servicio. Por ejemplo, el encabezado `X-Forwarded-For` incluye información sobre las direcciones IP que reenviaron la solicitud. Este encabezado es usado por los proxies para transportar una cadena de información al servicio final e incluye la IP de todos los proxies utilizados, incluida la dirección real del cliente. El problema es: a veces es difícil identificar la IP remota del cliente y no existe una regla específica para identificar este encabezado. Se recomienda encarecidamente leer la documentación de los encabezados que está a punto de implementar a continuación: - Lea sobre el encabezado `X-Forwarded-For` [aquí](https://developer.mozilla.org/en-US/docs/es/Web/HTTP/Headers/X-Forwarded-For#security_and_privacy_concerns). - Lea sobre el encabezado `X-Forwarded-Host` [aquí](https://developer.mozilla.org/en-US/docs/es/Web/HTTP/Headers/X-Forwarded-Host). - Lea sobre el encabezado `X-Forwarded-Proto` [aquí](https://developer.mozilla.org/en-US/docs/es/Web/HTTP/Headers/X-Forwarded-Proto). ## La clase ForwardingResolver Esta clase tiene tres métodos virtuales que permiten la implementación más adecuada para cada servicio. Cada método es responsable de resolver información de la solicitud a través de un proxy: la dirección IP del cliente, el host de la solicitud y el protocolo de seguridad utilizado. Por defecto, Sisk siempre usará la información de la solicitud original, sin resolver ningún encabezado. El ejemplo a continuación muestra cómo se puede usar esta implementación. Este ejemplo resuelve la IP del cliente mediante el encabezado `X-Forwarded-For` y lanza un error cuando se han reenviado más de una IP en la solicitud. > [!IMPORTANT] > No utilice este ejemplo en código de producción. Siempre verifique si la implementación es adecuada para su uso. Lea la documentación del encabezado antes de implementarlo. ```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]); } } } ``` --- # Manejadores del servidor HTTP Source: https://docs.sisk-framework.org/es/docs/advanced/http-server-handlers.html En la versión 0.16 de Sisk, hemos introducido la clase `HttpServerHandler`, que tiene como objetivo ampliar el comportamiento general de Sisk y proporcionar manejadores de eventos adicionales a Sisk, como el manejo de solicitudes Http, routers, bolsas de contexto y más. La clase concentra los eventos que ocurren durante la vida útil de todo el servidor HTTP y también de una solicitud. El protocolo Http no tiene sesiones, y por lo tanto no es posible preservar información de una solicitud a otra. Sisk, por ahora, ofrece una forma de que implementes sesiones, contextos, conexiones a bases de datos y otros proveedores útiles para ayudar en tu trabajo. Consulte [esta página](https://docs.sisk-framework.org/api/Sisk.Core.Http.Handlers.HttpServerHandler.md) para leer dónde se dispara cada evento y cuál es su propósito. También puede ver el [ciclo de vida de una solicitud HTTP](https://docs.sisk-framework.org/es/docs/advanced/request-lifecycle.md) para entender qué ocurre con una solicitud y dónde se disparan los eventos. El servidor HTTP le permite usar varios manejadores al mismo tiempo. Cada llamada de evento es síncrona, es decir, bloqueará el hilo actual para cada solicitud o contexto hasta que todos los manejadores asociados a esa función se ejecuten y completen. A diferencia de los RequestHandlers, no pueden aplicarse a algunos grupos de rutas o rutas específicas. En su lugar, se aplican a todo el servidor HTTP. Puede aplicar condiciones dentro de su Http Server Handler. Además, los singletons de cada HttpServerHandler se definen para cada aplicación Sisk, de modo que solo existe una instancia por `HttpServerHandler`. Un ejemplo práctico de uso de HttpServerHandler es disponer automáticamente una conexión a la base de datos al final de la solicitud. ```cs // DatabaseConnectionHandler.cs public class DatabaseConnectionHandler : HttpServerHandler { protected override void OnHttpRequestClose(HttpServerExecutionResult result) { var requestBag = result.Request.Context.RequestBag; // verifica si la solicitud ha definido un DbContext // en su bolsa de contexto 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()); } } ``` Con el código anterior, la extensión `GetDbContext` permite crear un contexto de conexión directamente desde el objeto HttpRequest. Una conexión no liberada puede causar problemas al trabajar con la base de datos, por lo que se termina en `OnHttpRequestClose`. Puede registrar un manejador en un servidor Http en su constructor o directamente con [HttpServer.RegisterHandler](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServer.RegisterHandler.md). ```cs // Program.cs class Program { static void Main(string[] args) { using var app = HttpServer.CreateBuilder() .UseHandler() .Build(); app.Router.MapInstance(new UserController()); app.Start(); } } ``` Con esto, la clase `UsersController` puede utilizar el contexto de base de datos de la siguiente manera: ```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."); } } ``` El código anterior usa métodos como `JsonOk` y `JsonMessage` que están incorporados en `ApiController`, que hereda de un `RouterController`: ```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 })); } } ``` ## Excepciones lanzadas por los manejadores Desde Sisk 1.7, las excepciones lanzadas por los manejadores del servidor se manejan de la siguiente manera: - Las excepciones lanzadas en `OnServerStarting`, `OnServerStarted` y `OnSetupRouter` detienen el inicio del servidor. Se envuelven en una `InvalidOperationException`, con la excepción original en `InnerException`. - Las excepciones lanzadas en otros eventos se escriben en [ErrorsLogsStream](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ErrorsLogsStream.md) y los manejadores restantes siguen siendo llamados, a menos que [ThrowExceptions](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ThrowExceptions.md) esté habilitado. Con `ThrowExceptions` habilitado, se envuelven en una `InvalidOperationException`. - Las excepciones lanzadas en `OnHttpRequestClose` y `OnException`, o al formatear los registros de acceso y error, nunca escapan al motor HTTP, incluso con `ThrowExceptions` habilitado. Se escriben en `ErrorsLogsStream`. En versiones anteriores, podían terminar el proceso o dejar la conexión abierta. Los desarrolladores pueden implementar sesiones, contextos y conexiones a bases de datos usando esta clase. El código proporcionado muestra un ejemplo práctico con el DatabaseConnectionHandler, automatizando la liberación de la conexión a la base de datos al final de cada solicitud. La integración es sencilla, con los manejadores registrados durante la configuración del servidor. La clase HttpServerHandler ofrece un conjunto de herramientas potente para gestionar recursos y ampliar el comportamiento de Sisk en aplicaciones HTTP. --- # Múltiples hosts de escucha por servidor Source: https://docs.sisk-framework.org/es/docs/advanced/multi-host-setup.html El Sisk Framework siempre ha soportado el uso de más de un host por servidor, es decir, un único servidor HTTP puede escuchar en varios puertos y cada puerto tiene su propio router y su propio servicio ejecutándose en él. De esta manera, es fácil separar responsabilidades y gestionar servicios en un único servidor HTTP con Sisk. El ejemplo a continuación muestra la creación de dos ListeningHosts, cada uno escuchando en un puerto diferente, con routers y acciones distintas. Lea [creación manual de su aplicación](https://docs.sisk-framework.org/es/docs/advanced/manual-setup.md) para entender los detalles de esta abstracción. ```cs static void Main(string[] args) { // crear dos hosts de escucha, cada uno con su propio router y // escucha en su propio puerto // 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!")); // crear una configuración de servidor y agregar ambos // hosts de escucha en ella // HttpServerConfiguration configuration = new HttpServerConfiguration(); configuration.ListeningHosts.Add(hostA); configuration.ListeningHosts.Add(hostB); // crear un servidor http que usa la // configuración especificada // HttpServer server = new HttpServer(configuration); // iniciar el servidor server.Start(); Console.WriteLine("Try to reach host A in {0}", server.ListeningPrefixes[0]); Console.WriteLine("Try to reach host B in {0}", server.ListeningPrefixes[1]); Thread.Sleep(-1); } ``` --- # Motores de servidor HTTP Source: https://docs.sisk-framework.org/es/docs/advanced/server-engines.html El marco de trabajo Sisk se divide en varios paquetes, donde el principal (Sisk.HttpServer) no incluye un servidor HTTP base - por defecto, [HttpListener](https://learn.microsoft.com/en-us/dotnet/api/system.net.httplistener?view=net-9.0) se utiliza como el motor principal de Sisk para realizar el papel de bajo nivel del servidor. El motor HTTP cumple el papel de la capa debajo de la capa de aplicación ofrecida por Sisk. Esta capa es responsable de la gestión de conexiones, serialización y deserialización de mensajes, control de cola de mensajes y comunicación con el socket de la máquina. La clase [HttpServerEngine](https://docs.sisk-framework.org/api/Sisk.Core.Http.Engine.HttpServerEngine.md) expone una API para implementar todas las funcionalidades necesarias de un motor HTTP para ser utilizado en capas superiores con Sisk, como enrutamiento, SSE, middlewares, etc. Estas funciones no son responsabilidad del motor HTTP, sino de un subconjunto de bibliotecas que utilizarán el motor HTTP como base para la ejecución. Con esta abstracción, es posible portar Sisk para ser utilizado con cualquier otro motor HTTP, escrito en .NET o no, como Kestrel, por ejemplo. Actualmente, Sisk sigue utilizando una abstracción del [HttpListener](https://learn.microsoft.com/en-us/dotnet/api/system.net.httplistener?view=net-9.0) nativo de .NET como el valor predeterminado para nuevos proyectos. Esta abstracción predeterminada conlleva algunos problemas específicos, como un comportamiento no especificado en diferentes plataformas (HttpListener tiene una implementación para Windows y otra para otras plataformas), falta de soporte para SSL y un rendimiento no muy agradable fuera de Windows. También está disponible una implementación experimental de un servidor de alto rendimiento escrito puramente en C# como un motor HTTP para Sisk, llamado el proyecto [Cadente](https://github.com/sisk-http/core/tree/main/cadente), que es un experimento de un servidor administrado que se puede utilizar con Sisk o no. ## Implementar un motor HTTP para Sisk Puedes crear un puente de conexión entre un servidor HTTP existente y Sisk extendiendo la clase [HttpServerEngine](https://docs.sisk-framework.org/api/Sisk.Core.Http.Engine.HttpServerEngine.md). Además de esta clase, también deberás implementar abstracciones para contextos, solicitudes y respuestas. Un ejemplo de abstracción completa está [disponible en GitHub](https://github.com/sisk-http/core/blob/main/src/Http/Engine/HttpListenerAbstractEngine.cs) para su visualización. Se ve así: ```csharp /// /// Proporciona una implementación de utilizando . /// public sealed class HttpListenerAbstractEngine : HttpServerEngine { private HttpListener _listener; private static Lazy shared = new Lazy ( () => new HttpListenerAbstractEngine () ); /// /// Obtiene la instancia compartida de la clase . /// public static HttpListenerAbstractEngine Shared => shared.Value; /// /// Inicializa una nueva instancia de la clase . /// public HttpListenerAbstractEngine () { _listener = new HttpListener { IgnoreWriteExceptions = true }; } /// public override TimeSpan IdleConnectionTimeout { get => _listener.TimeoutManager.IdleConnection; set => _listener.TimeoutManager.IdleConnection = value; } // ... } ``` ## Elegir un bucle de eventos Durante la creación de un motor HTTP, el servidor escuchará las solicitudes en un bucle y creará contextos para manejar cada una de ellas en hilos separados. Para esto, deberás elegir un [HttpServerEngineContextEventLoopMechanism](https://docs.sisk-framework.org/api/Sisk.Core.Http.Engine.HttpServerEngineContextEventLoopMechanism.md): - `InlineAsynchronousGetContext` el bucle de eventos es lineal - las llamadas de manejo de contexto HTTP ocurren en un bucle asíncrono. - `UnboundAsynchronousGetContext` el bucle de eventos se transmite a través de los métodos `BeginGetContext` y `EndGetContext`. ```csharp public override HttpServerEngineContextEventLoopMechanism EventLoopMechanism => HttpServerEngineContextEventLoopMechanism.UnboundAsynchronousGetContext; ``` No necesitas implementar ambos bucles de eventos. Elige el que más sentido tenga para tu motor HTTP. ## Pruebas Después de vincular tu motor HTTP, es esencial realizar pruebas para asegurarte de que todas las funcionalidades de Sisk tengan un comportamiento idéntico al utilizar otros motores. **Es extremadamente importante** tener el mismo comportamiento de Sisk para diferentes motores HTTP. Puedes visitar el repositorio de pruebas en [GitHub](https://github.com/sisk-http/core/tree/main/tests).