# Sisk Framework documentation > Sisk is a lightweight, agnostic and robust .NET web development framework. This file contains the complete Sisk documentation (Português). The API reference is not included; read https://docs.sisk-framework.org/api/index.md for the type index. --- # Começando Source: https://docs.sisk-framework.org/pt-br/docs/getting-started.html Bem-vindo à documentação do Sisk! Sisk é um framework HTTP leve e de código aberto para .NET. Você pode usá-lo para criar um serviço web independente, incorporar um módulo HTTP dentro de uma aplicação existente ou executar um serviço atrás de um proxy reverso com apenas a configuração que você precisa. Os valores do Sisk incluem transparência de código, modularidade, desempenho e escalabilidade. Ele pode lidar com diferentes estilos de aplicação, incluindo APIs RESTful, serviços JSON‑RPC, WebSockets, Server‑Sent Events e serviço de arquivos estáticos. Suas principais funcionalidades incluem: | Recurso | Descrição | | ------- | --------- | | [Routing](https://docs.sisk-framework.org/pt-br/docs/fundamentals/routing.md) | Um roteador de caminhos que suporta prefixos, métodos personalizados, variáveis de caminho, conversores de valores e mais. | | [Request Handlers](https://docs.sisk-framework.org/pt-br/docs/fundamentals/request-handlers.md) | Também conhecido como *middlewares*, fornece uma interface para criar seus próprios manipuladores de requisição que atuam antes ou depois de uma ação. | | [Compression](https://docs.sisk-framework.org/pt-br/docs/fundamentals/responses.md#gzip-deflate-and-brotli-compression) | Comprima facilmente o conteúdo das respostas com o Sisk. | | [Web sockets](https://docs.sisk-framework.org/pt-br/docs/features/websockets.md) | Fornece rotas que aceitam web‑sockets completos, para leitura e escrita no cliente. | | [Server-sent events](https://docs.sisk-framework.org/pt-br/docs/features/server-sent-events.md) | Fornece o envio de eventos do servidor para clientes que suportam o protocolo SSE. | | [Logging](https://docs.sisk-framework.org/pt-br/docs/features/logging.md) | Log simplificado. Registre erros, acessos, defina rotação de logs por tamanho, múltiplos fluxos de saída para o mesmo log, e mais. | | [Multi-host](https://docs.sisk-framework.org/pt-br/docs/advanced/multi-host-setup.md) | Tenha um servidor HTTP para múltiplas portas, e cada porta com seu próprio roteador, e cada roteador com sua própria aplicação. | | [Server handlers](https://docs.sisk-framework.org/pt-br/docs/advanced/http-server-handlers.md) | Estenda sua própria implementação do servidor HTTP. Personalize com extensões, melhorias e novos recursos. | ## Primeiros passos Sisk pode ser executado em qualquer ambiente .NET. Neste guia, ensinaremos como criar uma aplicação Sisk usando .NET. Se ainda não o instalou, por favor baixe o SDK [aqui](https://dotnet.microsoft.com/en-us/download/dotnet/7.0). Neste tutorial, abordaremos como criar uma estrutura de projeto, receber uma requisição, obter um parâmetro de URL e enviar uma resposta. Este guia focará na construção de um servidor simples usando C#. Você também pode usar sua linguagem de programação favorita. > [!NOTE] > Você pode estar interessado em um projeto de início rápido. Confira [este repositório](https://github.com/sisk-http/quickstart) para mais informações. ## Criando um Projeto Vamos chamar nosso projeto de "My Sisk Application". Depois de configurar o .NET, você pode criar seu projeto com o seguinte comando: ```bash dotnet new console -n my-sisk-application ``` Em seguida, navegue até o diretório do seu projeto e instale o Sisk usando a ferramenta de utilitário do .NET: ```bash cd my-sisk-application dotnet add package Sisk.HttpServer ``` Você pode encontrar maneiras adicionais de instalar o Sisk em seu projeto [aqui](https://www.nuget.org/packages/Sisk.HttpServer/). Agora, vamos criar uma instância do nosso servidor HTTP. Para este exemplo, vamos configurá-lo para escutar na porta 5000. ## Construindo o Servidor HTTP Sisk permite que você construa sua aplicação passo a passo manualmente, pois ele roteia para o objeto HttpServer. No entanto, isso pode não ser muito conveniente para a maioria dos projetos. Portanto, podemos usar o método builder, que facilita colocar nossa aplicação em funcionamento. ```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(); } } ``` É importante entender cada componente vital do Sisk. Mais adiante neste documento, você aprenderá mais sobre como o Sisk funciona. ## Configuração Manual (avançada) Você pode aprender como cada mecanismo do Sisk funciona nesta [seção](https://docs.sisk-framework.org/pt-br/docs/advanced/manual-setup.md) da documentação, que explica o comportamento e as relações entre o HttpServer, Router, ListeningPort e outros componentes. --- # Instalando Source: https://docs.sisk-framework.org/pt-br/docs/installing.html Você pode instalar o Sisk por meio do Nuget, dotnet cli ou [outras opções](https://www.nuget.org/packages/Sisk.HttpServer/). Você pode configurar facilmente o ambiente do Sisk executando este comando no console do desenvolvedor: ```sh dotnet add package Sisk.HttpServer ``` Este comando irá instalar a versão mais recente do Sisk no seu projeto. --- # Suporte Nativo AOT Source: https://docs.sisk-framework.org/pt-br/docs/native-aot.html [.NET Native AOT](https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/) permite a publicação de aplicativos .NET nativos que são autossuficientes e não requerem o tempo de execução do .NET instalado no host de destino. Além disso, o Native AOT fornece benefícios como: - Aplicativos muito menores - Inicialização significativamente mais rápida - Consumo de memória mais baixo O Sisk Framework, por sua natureza explícita, permite o uso de Native AOT para quase todos os seus recursos sem exigir rework no código-fonte para adaptá-lo ao Native AOT. ## Recursos não suportados No entanto, o Sisk usa reflexão, embora mínima, para alguns recursos. Os recursos mencionados abaixo podem estar parcialmente disponíveis ou completamente indisponíveis durante a execução de código nativo: - [Auto-escaneamento de módulos](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.AutoScanModules.md) do roteador: este recurso escaneia os tipos incorporados na Assembly em execução e registra os tipos que são [módulos do roteador](https://docs.sisk-framework.org/pt-br/docs/fundamentals/routing.md). Este recurso requer tipos que possam ser excluídos durante a redução da Assembly. Todos os outros recursos são compatíveis com o AOT no Sisk. É comum encontrar um ou outro método que dá um aviso de AOT, mas o mesmo, se não for mencionado aqui, tem uma sobrecarga que indica a passagem de um tipo, parâmetro ou informação de tipo que ajuda o compilador AOT a compilar o objeto. --- # Implantando sua Aplicação Sisk Source: https://docs.sisk-framework.org/pt-br/docs/deploying.html O processo de implantar uma aplicação Sisk consiste em publicar seu projeto em produção. Embora o processo seja relativamente simples, é importante notar detalhes que podem ser letais para a segurança e estabilidade da infraestrutura de implantação. Idealmente, você deve estar pronto para implantar sua aplicação na nuvem, após realizar todos os testes possíveis para ter sua aplicação pronta. ## Publicando sua aplicação Publicar sua aplicação ou serviço Sisk é gerar binários prontos e otimizados para produção. Neste exemplo, vamos compilar os binários para produção para executar em uma máquina que tem o .NET Runtime instalado. Você precisará ter o .NET SDK instalado em sua máquina para compilar sua aplicação, e o .NET Runtime instalado no servidor de destino para executar sua aplicação. Você pode aprender como instalar o .NET Runtime em seu servidor Linux [aqui](https://learn.microsoft.com/en-us/dotnet/core/install/linux), [Windows](https://learn.microsoft.com/en-us/dotnet/core/install/windows?tabs=net70) e [Mac OS](https://learn.microsoft.com/en-us/dotnet/core/install/macos). No diretório onde seu projeto está localizado, abra um terminal e use o comando .NET publish: ```shell $ dotnet publish -r linux-x64 -c Release ``` Isso gerará seus binários dentro de `bin/Release/publish/linux-x64`. > [!NOTE] > Se sua aplicação estiver executando usando o pacote Sisk.ServiceProvider, você deve copiar seu `service-config.json` para o servidor de hospedagem junto com todos os binários gerados pelo `dotnet publish`. > Você pode deixar o arquivo pré-configurado, com variáveis de ambiente, portas e hosts de escuta, e configurações adicionais do servidor. A próxima etapa é levar esses arquivos para o servidor onde sua aplicação será hospedada. Depois disso, dê permissões de execução para o seu arquivo binário. Neste caso, vamos considerar que o nome do nosso projeto é "my-app": ```shell $ cd /home/htdocs $ chmod +x my-app $ ./my-app ``` Depois de executar sua aplicação, verifique se ela produz alguma mensagem de erro. Se não produzir, é porque sua aplicação está executando. Neste ponto, provavelmente não será possível acessar sua aplicação pela rede externa fora do seu servidor, pois as regras de acesso, como Firewall, não foram configuradas. Vamos considerar isso nas próximas etapas. Você deve ter o endereço do host virtual onde sua aplicação está escutando. Isso é definido manualmente na aplicação e depende de como você está instanciando seu serviço Sisk. Se você **não** estiver usando o pacote Sisk.ServiceProvider, você deve encontrar onde definiu sua instância de HttpServer: ```cs HttpServer server = HttpServer.Emit(5000, out HttpServerConfiguration config, out var host, out var router); // sisk deve escutar em http://localhost:5000/ ``` Associando um ListeningHost manualmente: ```cs config.ListeningHosts.Add(new ListeningHost("https://localhost:5000/", router)); ``` Ou se você estiver usando o pacote Sisk.ServiceProvider, em seu `service-config.json`: ```json { "Server": { }, "ListeningHost": { "Ports": [ "http://localhost:5000/" ] } } ``` A partir disso, podemos criar um proxy reverso para escutar seu serviço e tornar o tráfego disponível sobre a rede aberta. ## Proxyando sua aplicação Proxyar seu serviço significa não expor diretamente seu serviço Sisk à rede externa. Essa prática é muito comum para implantações de servidor porque: - Permite associar um certificado SSL à sua aplicação; - Cria regras de acesso antes de acessar o serviço e evitar sobrecargas; - Controla a largura de banda e os limites de solicitação; - Separa os balanceadores de carga para sua aplicação; - Previne danos de segurança à infraestrutura de falha. Você pode servir sua aplicação por meio de um proxy reverso como [Nginx](https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/linux-nginx?view=aspnetcore-7.0&tabs=linux-ubuntu#install-nginx) ou [Apache](https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/linux-apache?view=aspnetcore-7.0), ou pode usar um túnel http-over-dns como [Cloudflared](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/install-and-setup/tunnel-guide/). Além disso, lembre-se de resolver corretamente os cabeçalhos de encaminhamento do seu proxy para obter as informações do cliente, como endereço IP e host, por meio de [resolvidores de encaminhamento](https://docs.sisk-framework.org/pt-br/docs/advanced/forwarding-resolvers.md). A próxima etapa após criar seu túnel, configurar o Firewall e ter sua aplicação em execução é criar um serviço para sua aplicação. > [!NOTE] > Usar certificados SSL diretamente no serviço Sisk em sistemas não-Windows não é possível. Isso é um ponto da implementação do HttpListener, que é o módulo central para como a gestão da fila HTTP é feita no Sisk, e essa implementação varia de sistema operacional para sistema operacional. Você pode usar SSL em seu serviço Sisk se [associar um certificado ao host virtual com IIS](https://learn.microsoft.com/en-us/iis/manage/configuring-security/how-to-set-up-ssl-on-iis). Para outros sistemas, usar um proxy reverso é altamente recomendado. ## Criando um serviço Criar um serviço fará com que sua aplicação esteja sempre disponível, mesmo após reiniciar sua instância de servidor ou uma falha não recuperável. Neste tutorial simples, vamos usar o conteúdo do tutorial anterior como um exemplo para manter seu serviço sempre ativo. 1. Acesse o diretório onde os arquivos de configuração do serviço estão localizados: ```sh cd /etc/systemd/system ``` 2. Crie seu arquivo `my-app.service` e inclua o conteúdo: ```ini {title="my-app.service"} [Unit] Description= [Service] # defina o usuário que lançará o serviço User= # o caminho do ExecStart não é relativo ao WorkingDirectory. # defina-o como o caminho completo para o arquivo executável WorkingDirectory=/home/htdocs ExecStart=/home/htdocs/my-app # defina o serviço para sempre reiniciar em caso de falha Restart=always RestartSec=3 [Install] WantedBy=multi-user.target ``` 3. Reinicie o módulo de gerenciamento de serviço: ```sh $ sudo systemctl daemon-reload ``` 4. Inicie seu novo serviço criado a partir do nome do arquivo que você definiu e verifique se ele está em execução: ```sh $ sudo systemctl start my-app $ sudo systemctl status my-app ``` 5. Agora, se sua aplicação estiver em execução ("Active: active"), habilite seu serviço para continuar em execução após uma reinicialização do sistema: ```sh $ sudo systemctl enable my-app ``` Agora você está pronto para apresentar sua aplicação Sisk a todos. --- # Trabalhando com SSL Source: https://docs.sisk-framework.org/pt-br/docs/ssl.html Trabalhar com SSL para desenvolvimento pode ser necessário ao atuar em contextos que exigem segurança, como a maioria dos cenários de desenvolvimento web. O Sisk opera sobre HttpListener, que não oferece suporte nativo a HTTPS, apenas HTTP. No entanto, existem soluções alternativas que permitem usar SSL no Sisk. Veja-as abaixo: ## Através do Sisk.Cadente.CoreEngine - Disponível em: Linux, macOS, Windows - Esforço: fácil É possível usar o motor experimental [**Cadente**](https://docs.sisk-framework.org/pt-br/docs/cadente.md) em projetos Sisk, sem exigir configuração adicional no computador ou no projeto. Você precisará instalar o pacote `Sisk.Cadente.CoreEngine` em seu projeto para poder usar o servidor Cadente no servidor Sisk. Para configurar SSL, você pode usar os métodos `UseSsl` e `UseEngine` do builder: ```csharp using var http = HttpServer.CreateBuilder() .UseEngine() .UseSsl(CertificateHelper.CreateTrustedDevelopmentCertificate("localhost")) ``` > Nota: este pacote ainda está na fase experimental. ## Através do IIS no Windows - Disponível em: Windows - Esforço: médio Se você está no Windows, pode usar o IIS para habilitar SSL no seu servidor HTTP. Para que isso funcione, é aconselhável que você siga [este tutorial](https://docs.sisk-framework.org/pt-br/docs/registering-namespace.md) antes, caso queira que sua aplicação escute em um host diferente de "localhost." Para que isso funcione, você deve instalar o IIS através dos recursos do Windows. O IIS está disponível gratuitamente para usuários do Windows e Windows Server. Para configurar SSL em sua aplicação, tenha o certificado SSL pronto, mesmo que seja autoassinado. Em seguida, você pode ver [como configurar SSL no IIS 7 ou superior](https://learn.microsoft.com/en-us/iis/manage/configuring-security/how-to-set-up-ssl-on-iis). ## Através do mitmproxy - Disponível em: Linux, macOS, Windows - Esforço: fácil **mitmproxy** é uma ferramenta de proxy de interceptação que permite a desenvolvedores e testadores de segurança inspecionar, modificar e registrar o tráfego HTTP e HTTPS entre um cliente (como um navegador web) e um servidor. Você pode usar o utilitário **mitmdump** para iniciar um proxy SSL reverso entre seu cliente e sua aplicação Sisk. 1. Primeiro, instale o [mitmproxy](https://mitmproxy.org/) em sua máquina. 2. Inicie sua aplicação Sisk. Para este exemplo, usaremos a porta 8000 como a porta HTTP insegura. 3. Inicie o servidor mitmproxy para escutar na porta segura 8001: ```sh mitmdump --mode reverse:http://localhost:8000/ -p 8001 ``` E você está pronto! Já pode acessar sua aplicação através de `https://localhost:8001/`. Sua aplicação não precisa estar em execução para você iniciar o `mitmdump`. Alternativamente, você pode adicionar uma referência ao [auxiliar mitmproxy](https://github.com/sisk-http/core/tree/main/extensions/Sisk.Helpers.mitmproxy) em seu projeto. Isso ainda requer que o mitmproxy esteja instalado em seu computador. ## Através do pacote Sisk.SslProxy - Disponível em: Linux, macOS, Windows - Esforço: fácil > [!IMPORTANT] > > O pacote Sisk.SslProxy está obsoleto em favor do pacote `Sisk.Cadente.CoreEngine` e não será mais mantido. O pacote Sisk.SslProxy é uma maneira simples de habilitar SSL em sua aplicação Sisk. No entanto, é um pacote **extremamente experimental**. Pode ser instável trabalhar com este pacote, mas você pode fazer parte da pequena porcentagem de pessoas que contribuirão para tornar este pacote viável e estável. Para começar, você pode instalar o pacote Sisk.SslProxy com: ```sh dotnet add package Sisk.SslProxy ``` > [!NOTE] > > Você deve habilitar "Include prerelease" no Gerenciador de Pacotes do Visual Studio para instalar o Sisk.SslProxy. Novamente, é um projeto experimental, portanto nem pense em colocá-lo em produção. No momento, o Sisk.SslProxy pode lidar com a maioria dos recursos do HTTP/1.1, incluindo HTTP Continue, Chunked-Encoding, WebSockets e SSE. Leia mais sobre o SslProxy [aqui](https://docs.sisk-framework.org/pt-br/docs/extensions/ssl-proxy.md). --- # Cadente Source: https://docs.sisk-framework.org/pt-br/docs/cadente.html Cadente é uma implementação experimental de ouvinte HTTP/1.1 gerenciado para Sisk. Ele serve como substituto para o `System.Net.HttpListener` padrão, oferecendo maior controle e flexibilidade, especialmente em plataformas não-Windows. ## Visão Geral Por padrão, o Sisk usa `HttpListener` (do `System.Net`) como seu mecanismo de servidor HTTP subjacente. Embora `HttpListener` seja estável e performático no Windows (onde ele usa o driver HTTP.sys do kernel), sua implementação no Linux e macOS é gerenciada e historicamente teve limitações, como falta de suporte nativo SSL (requerendo um proxy reverso como Nginx ou Sisk.SslProxy) e características de desempenho variadas. Cadente visa resolver esses problemas fornecendo um servidor HTTP/1.1 totalmente gerenciado escrito em C#. Seus principais objetivos são: - **Suporte Nativo SSL:** Funciona em todas as plataformas sem precisar de proxies externos ou configuração complexa. - **Consistência Cross-Platform:** Comportamento idêntico no Windows, Linux e macOS. - **Desempenho:** Projetado para ser uma alternativa de alto desempenho ao `HttpListener` gerenciado. - **Independência:** Desacoplado do `System.Net.HttpListener`, isolando o Sisk de possíveis depreciações futuras ou falta de manutenção desse componente no .NET. > [!WARNING] > **Status Experimental** > > Cadente está atualmente em uma fase experimental (Beta). Ele não é recomendado ainda para ambientes de produção críticos. A API e o comportamento podem mudar. ## Instalação Cadente está disponível como um pacote separado. Para usá-lo com Sisk, você precisa do pacote `Sisk.Cadente.CoreEngine`. ```bash dotnet add package Sisk.Cadente.CoreEngine --prerelease ``` ## Usando com Sisk Para usar Cadente como o mecanismo de servidor HTTP para sua aplicação Sisk, você precisa configurar o `HttpServer` para usar `CadenteHttpServerEngine` em vez do mecanismo padrão. O `CadenteHttpServerEngine` adapta o `HttpHost` do Cadente à abstração `HttpServerEngine` necessária pelo 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(); ``` ### Configuração Avançada Você pode personalizar a instância subjacente `HttpHost` passando uma ação de configuração para o construtor do `CadenteHttpServerEngine`. Isso é útil para configurar tempos de espera ou outros ajustes de nível baixo. ```csharp using var engine = new CadenteHttpServerEngine(host => { // Configure tempos de espera de leitura/escrita do cliente host.TimeoutManager.ClientReadTimeout = TimeSpan.FromSeconds(30); host.TimeoutManager.ClientWriteTimeout = TimeSpan.FromSeconds(30); }); ``` ## Uso Autônomo Embora projetado principalmente para Sisk, Cadente pode ser usado como um servidor HTTP autônomo (semelhante ao `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 writer = new StreamWriter(context.Response.GetResponseStream()); await writer.WriteLineAsync("Olá, mundo!"); } } ``` --- # Configurando reservas de namespace no Windows Source: https://docs.sisk-framework.org/pt-br/docs/registering-namespace.html > [!NOTE] > Esta configuração é opcional e só é necessária quando você deseja que o Sisk escute em hosts diferentes de "localhost" no Windows usando o mecanismo HttpListener. O Sisk funciona com a interface de rede HttpListener, que vincula um host virtual ao sistema para escutar solicitações. No Windows, essa vinculação é um pouco restritiva, permitindo apenas que o localhost seja vinculado como um host válido. Ao tentar escutar em outro host, um erro de acesso negado é lançado no servidor. Este tutorial explica como conceder autorização para escutar em qualquer host que você desejar no sistema. ```bat {title="Namespace Setup.bat"} @echo off :: insira o prefixo aqui, sem espaços ou aspas SET PREFIX= SET DOMAIN=%ComputerName%\%USERNAME% netsh http add urlacl url=%PREFIX% user=%DOMAIN% pause ``` Onde em `PREFIX` está o prefixo ("Listening Host->Port") que seu servidor escutará. Ele deve ser formatado com o esquema da URL, host, porta e uma barra no final, exemplo: ```bat {title="Namespace Setup.bat"} SET PREFIX=http://my-application.example.test/ ``` Para que sua aplicação possa escutar através de: ```csharp {title="Program.cs"} class Program { static async Task Main(string[] args) { using var app = HttpServer.CreateBuilder() .UseListeningPort("http://my-application.example.test/") .Build(); app.Router.MapGet("/", request => { return new HttpResponse() { Status = 200, Content = new StringContent("Hello, world!") }; }); await app.StartAsync(); } } ``` --- # Changelogs Source: https://docs.sisk-framework.org/pt-br/docs/changelogs.html Todas as alterações feitas no Sisk são registradas por meio do changelog. Você pode visualizar os changelogs de todas as versões do Sisk [aqui](https://github.com/sisk-http/archive/tree/master/changelogs). --- # Perguntas Frequentes Source: https://docs.sisk-framework.org/pt-br/docs/faq.html Perguntas frequentes sobre Sisk. ## O Sisk é de código aberto? Totalmente. Todo o código-fonte utilizado pelo Sisk é publicado e atualizado frequentemente no [GitHub](https://github.com/sisk-http). ## Contribuições são aceitas? Desde que sejam compatíveis com a [filosofia do Sisk](/), todas as contribuições são muito bem-vindas! As contribuições não precisam ser apenas código! Você pode contribuir com documentação, testes, traduções, doações e posts, por exemplo. ## O Sisk é financiado? Não. Nenhuma organização ou projeto atualmente patrocina o Sisk. ## Posso usar o Sisk em produção? Absolutamente. O projeto está em desenvolvimento há mais de três anos e teve testes intensivos em aplicações comerciais que estão em produção desde então. O Sisk é usado em projetos comerciais importantes como infraestrutura principal. Um guia sobre como [implantar](https://docs.sisk-framework.org/pt-br/docs/deploying.md) em diferentes sistemas e ambientes foi escrito e está disponível. ## O Sisk tem autenticação, monitoramento e serviços de banco de dados? Não. O Sisk não tem nenhum desses. É um framework para desenvolver aplicações web HTTP, mas ainda é um framework minimalista que entrega o que é necessário para que sua aplicação funcione. Você pode implementar todos os serviços que desejar usando qualquer biblioteca de terceiros que preferir. O Sisk foi feito para ser agnóstico, flexível e funcionar com qualquer coisa. ## Por que devo usar o Sisk em vez de ? Não sei. Você me diz. O Sisk foi criado para atender a um cenário genérico para aplicações web HTTP em .NET. Projetos estabelecidos, como o ASP.NET, resolvem vários problemas, mas com diferentes vieses. Diferentemente de frameworks maiores, o Sisk exige que o usuário saiba o que está fazendo e construindo. Noções básicas de desenvolvimento web e do protocolo HTTP são essenciais para trabalhar com o Sisk. O Sisk é mais próximo do Express do Node.js do que do ASP.NET Core. É uma abstração de alto nível que permite criar aplicações com lógica HTTP que você deseja. ## O que preciso aprender para usar o Sisk? Você precisa dos básicos de: - Desenvolvimento web (HTTP, Restful, etc.) - .NET É isso. Tendo uma noção desses dois tópicos, você pode dedicar algumas horas para desenvolver uma aplicação avançada com o Sisk. ## Posso desenvolver aplicações comerciais com o Sisk? Absolutamente. O Sisk foi criado sob a licença MIT, o que significa que você pode usar o Sisk em qualquer projeto comercial, comercial ou não comercial, sem a necessidade de uma licença proprietária. O que pedimos é que em algum lugar da sua aplicação, você tenha um aviso dos projetos de código aberto utilizados em seu projeto, e que o Sisk esteja lá. --- # Roteamento Source: https://docs.sisk-framework.org/pt-br/docs/fundamentals/routing.html O [Router](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.md) é o primeiro passo na construção do servidor. Ele é responsável por armazenar objetos [Route](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Route.md), que são pontos de extremidade que mapeiam URLs e seus métodos para ações executadas pelo servidor. Cada ação é responsável por receber uma requisição e entregar uma resposta ao cliente. As rotas são pares de expressões de caminho (“padrão de caminho”) e o método HTTP que elas podem escutar. Quando uma requisição é feita ao servidor, ele tentará encontrar uma rota que corresponda à requisição recebida, então chamará a ação dessa rota e entregará a resposta resultante ao cliente. Existem várias maneiras de definir rotas no Sisk: elas podem ser estáticas, dinâmicas ou auto‑escanadas, definidas por atributos, ou diretamente no objeto Router. ```cs Router mainRouter = new Router(); // mapeia a rota GET / para a ação a seguir mainRouter.MapGet("/", request => { return new HttpResponse("Hello, world!"); }); ``` Para entender o que uma rota pode fazer, precisamos entender o que uma requisição pode fazer. Um [HttpRequest](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.md) conterá tudo o que você precisa. O Sisk também inclui alguns recursos extras que aceleram o desenvolvimento geral. Para cada ação recebida pelo servidor, um delegate do tipo [RouteAction](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouteAction.md) será chamado. Esse delegate contém um parâmetro que contém um [HttpRequest](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.md) com todas as informações necessárias sobre a requisição recebida pelo servidor. O objeto resultante desse delegate deve ser um [HttpResponse](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpResponse.md) ou um objeto que mapeie para ele através de [tipos de resposta implícitos](https://docs.sisk-framework.org/pt-br/docs/fundamentals/responses.md#implicit-response-types). ## Correspondência de rotas Quando uma requisição é recebida pelo servidor HTTP, o Sisk procura uma rota que satisfaça a expressão do caminho recebido pela requisição. A expressão é sempre testada entre a rota e o caminho da requisição, sem considerar a string de consulta. Esse teste não tem prioridade e é exclusivo a uma única rota. Quando nenhuma rota corresponde àquela requisição, a resposta de [Router.NotFoundErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.NotFoundErrorHandler.md) é retornada ao cliente. Quando o padrão de caminho corresponde, mas o método HTTP não, a resposta de [Router.MethodNotAllowedErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.MethodNotAllowedErrorHandler.md) é enviada de volta ao cliente. O Sisk verifica a possibilidade de colisões de rotas para evitar esses problemas. Ao definir rotas, o Sisk procurará por rotas possíveis que possam colidir com a rota que está sendo definida. Esse teste inclui a verificação do caminho e do método que a rota está configurada para aceitar. ### Criando rotas usando padrões de caminho Para novas aplicações, prefira os métodos `Map*`. Eles mantêm o método HTTP visível no ponto de chamada e correspondem à API atual do `Router`. Os métodos mais antigos `SetRoute` ainda existem como wrappers de compatibilidade, mas novos exemplos devem usar `Map`, `MapGet`, `MapPost`, `MapPut`, `MapDelete`, `MapPatch`, `MapAny`, `MapOptions` ou `MapHead`. ```cs // Métodos Map* são a forma usual de definir rotas 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 vazio }); // Map também pode receber uma instância de Route quando você precisar de opções de rota. mainRouter.Map(Route.Get("/image.png", (request) => { var imageStream = File.OpenRead("image.png"); return new HttpResponse() { // o conteúdo interno StreamContent // o stream é descartado após o envio // da resposta. Content = new StreamContent(imageStream) }; })); // múltiplos 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}!"); }); ``` A propriedade [RouteParameters](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.RouteParameters.md) de HttpRequest contém todas as informações sobre as variáveis de caminho da requisição recebida. Todo caminho recebido pelo servidor é normalizado antes da execução do teste de padrão de caminho, seguindo estas regras: - Todos os segmentos vazios são removidos do caminho, por exemplo: `////foo//bar` torna‑se `/foo/bar`. - A correspondência de caminho é **sensível a maiúsculas/minúsculas**, a menos que [Router.MatchRoutesIgnoreCase](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.MatchRoutesIgnoreCase.md) esteja definido como `true`. As propriedades [Query](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Query.md) e [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) retornam um objeto [StringValueCollection](https://docs.sisk-framework.org/api/Sisk.Core.Entity.StringValueCollection.md), onde cada propriedade indexada retorna um [StringValue](https://docs.sisk-framework.org/api/Sisk.Core.Entity.StringValue.md) não nulo, que pode ser usado como uma opção/monad para converter seu valor bruto em um objeto gerenciado. O exemplo abaixo lê o parâmetro de rota “id” e obtém um `Guid` a partir dele. Se o parâmetro não for um Guid válido, uma exceção é lançada, e um erro 500 é retornado ao cliente se o servidor não estiver tratando [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] > Os caminhos têm sua barra final `/` ignorada tanto na requisição quanto no caminho da rota, ou seja, se você tentar acessar uma rota definida como `/index/page` também poderá acessá‑la usando `/index/page/`. > > Você também pode forçar URLs a terminarem com `/` habilitando [HttpServerConfiguration.ForceTrailingSlash](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ForceTrailingSlash.md). ### Criando rotas usando instâncias de classe Você também pode definir rotas dinamicamente usando reflexão com o atributo [RouteAttribute](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouteAttribute.md). Dessa forma, a instância de uma classe cujos métodos implementam esse atributo terá suas rotas definidas no router de destino. Para que um método seja definido como rota, ele deve ser marcado com um [RouteAttribute](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouteAttribute.md), como o próprio atributo ou um [RouteGetAttribute](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouteGetAttribute.md). O método pode ser estático, de instância, público ou privado. Use `MapInstance` quando quiser mapear métodos de rota de instância e estáticos a partir de um objeto. Use `MapType` quando quiser mapear apenas métodos de rota estáticos de um tipo. ```cs {title="Controller/MyController.cs"} public class MyController { // corresponderá ao GET / [RouteGet] HttpResponse Index(HttpRequest request) { HttpResponse res = new HttpResponse(); res.Content = new StringContent("Index!"); return res; } // métodos estáticos também funcionam [RouteGet("/hello")] static HttpResponse Hello(HttpRequest request) { HttpResponse res = new HttpResponse(); res.Content = new StringContent("Hello world!"); return res; } } ``` A linha abaixo definirá tanto os métodos `Index` quanto `Hello` de `MyController` como rotas, já que ambos estão marcados como rotas, e uma instância da classe foi fornecida, não seu tipo. Se o tipo tivesse sido fornecido em vez de uma instância, apenas os métodos estáticos seriam definidos. ```cs var myController = new MyController(); mainRouter.MapInstance(myController); ``` Para mapear apenas métodos de rota estáticos de um tipo, use: ```cs mainRouter.MapType(); ``` Desde a versão 0.16 do Sisk, é possível habilitar AutoScan, que buscará classes definidas pelo usuário que implementem `RouterModule` e as associará automaticamente ao router. Isso não é suportado com compilação AOT. ```cs mainRouter.AutoScanModules(); ``` A instrução acima buscará todos os tipos que implementam `ApiController`, mas **não o próprio tipo**. Os dois parâmetros opcionais indicam como o método buscará esses tipos. O primeiro argumento implica o Assembly onde os tipos serão buscados e o segundo indica a forma como os tipos serão definidos. ## Rotas Regex Em vez de usar os métodos padrão de correspondência de caminho HTTP, você pode marcar uma rota para ser interpretada com Regex. ```cs Route indexRoute = new RegexRoute(RouteMethod.Get, @"\/[a-z]+\/", IndexPage); mainRouter.Map(indexRoute); ``` Ou com a classe [RegexRoute](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RegexRoute.md): ```cs mainRouter.Map(new RegexRoute(RouteMethod.Get, @"\/[a-z]+\/", request => { return new HttpResponse("hello, world"); })); ``` Você também pode capturar grupos do padrão regex nos conteúdos 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}"); } } ``` ## Prefixando rotas Você pode prefixar todas as rotas em uma classe ou módulo com o atributo [RoutePrefix](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RoutePrefixAttribute.md) e definir o prefixo como uma string. Veja o exemplo abaixo usando a arquitetura 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() { ... } } ``` No exemplo acima, o parâmetro HttpResponse é omitido em favor de ser usado através do contexto global [HttpContext.Current](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpContext.Current.md). Leia mais na seção que se segue. ## Rotas sem parâmetro de requisição Rotas podem ser definidas sem o parâmetro [HttpRequest](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.md) e ainda assim ser possível obter a requisição e seus componentes no contexto da requisição. Vamos considerar uma abstração `ControllerBase` que serve como base para todos os controladores de uma API, e que fornece a propriedade `Request` para obter o [HttpRequest](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.md) atual. ```cs {title="Controller/ControllerBase.cs"} public abstract class ControllerBase { // obtém a requisição da thread atual public HttpRequest Request { get => HttpContext.Current.Request; } // a linha abaixo, quando chamada, obtém o banco de dados da sessão HTTP atual, // ou cria um novo caso não exista public DbContext Database { get => HttpContext.Current.RequestBag.GetOrAdd(); } } ``` E para que todos os seus descendentes possam usar a sintaxe de rota sem o parâmetro de requisição: ```cs {title="Controller/UsersController.cs"} [RoutePrefix("/api/users")] public class UsersController : ControllerBase { [RoutePost] public async Task Create() { // lê os dados JSON da requisição atual UserCreationDto? user = await Request.GetJsonContentAsync(); ... Database.Users.Add(user); return new HttpResponse(201); } } ``` Mais detalhes sobre o contexto atual e injeção de dependência podem ser encontrados no tutorial de [injeção de dependência](https://docs.sisk-framework.org/pt-br/docs/features/instancing.md). ## Rotas de qualquer método Você pode definir uma rota para ser correspondida apenas pelo seu caminho e ignorar o método HTTP. Isso pode ser útil para você fazer validação de método dentro do callback da rota. ```cs // corresponderá a / em qualquer método HTTP mainRouter.MapAny("/", callbackFunction); ``` ## Rotas de qualquer caminho Rotas de qualquer caminho testam qualquer caminho recebido pelo servidor HTTP, sujeito ao método da rota sendo testado. Se o método da rota for RouteMethod.Any e a rota usar [Route.AnyPath](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Route.AnyPath.md) em sua expressão de caminho, essa rota ouvirá todas as requisições do servidor HTTP, e nenhuma outra rota poderá ser definida. ```cs // a rota a seguir corresponderá a todas as requisições POST mainRouter.Map(RouteMethod.Post, Route.AnyPath, callbackFunction); ``` ## Ignorar diferenciação de maiúsculas/minúsculas na correspondência de rotas Por padrão, a interpretação de rotas com requisições diferencia maiúsculas de minúsculas. Para fazer com que ignore isso, habilite esta opção: ```cs mainRouter.MatchRoutesIgnoreCase = true; ``` Isso também habilitará a opção `RegexOptions.IgnoreCase` para rotas onde a correspondência é feita por regex. ## Manipulador de callback “Not Found” (404) Você pode criar um callback customizado para quando uma requisição não corresponder a nenhuma rota conhecida. ```cs mainRouter.NotFoundErrorHandler = () => { return new HttpResponse(404) { // Desde v0.14 Content = new HtmlContent("

Not found

") // versões anteriores Content = new StringContent("

Not found

", Encoding.UTF8, "text/html") }; }; ``` ## Manipulador de callback “Method Not Allowed” (405) Você também pode criar um callback customizado para quando uma requisição corresponde ao caminho, mas não ao método. ```cs mainRouter.MethodNotAllowedErrorHandler = (context) => { return new HttpResponse(405) { Content = new StringContent($"Method not allowed for this route.") }; }; ``` ## Tratamento de Erros Exceções podem ser lançadas dentro do ciclo de vida de uma requisição, que vai desde o manipulador de requisição pré‑execução, passando pela ação do router, até os manipuladores de requisição pós‑execução e manipuladores de valor. Essas exceções são gerenciadas pelo mecanismo: - Se [HttpServerConfiguration.ThrowExceptions](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ThrowExceptions.md) for `true`, as exceções serão lançadas normalmente e não serão capturadas pelo Sisk, e o servidor HTTP pode ser interrompido se a exceção não for tratada. - Se [HttpServerConfiguration.ThrowExceptions](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ThrowExceptions.md) for `false`, as exceções serão capturadas e tratadas pelo Sisk. Depois disso, se `Router.CallbackErrorHandler` estiver definido, ele será chamado com a exceção capturada e o contexto da requisição, e **não** será encaminhado para a saída de erro padrão. Se `Router.CallbackErrorHandler` não estiver definido, a exceção será encaminhada para a saída de erro padrão, e o cliente receberá uma resposta HTTP 500. Se a saída de erro padrão não estiver definida, o erro será silenciosamente ignorado. Nota: dentro de `Router.CallbackErrorHandler`, você pode definir o modo de log para erros, log de acesso, ambos ou nenhum, e alterar o comportamento padrão de escrita de logs: ```csharp router.CallbackErrorHandler = (ex, ctx) => { ctx.LogMode = LogOutput.Both; // sobrescreve o modo de log para registrar o erro tanto no log de acesso quanto no de erro } ``` ## Manipulador interno de erro Callbacks de rota podem lançar erros durante a execução do servidor. Se não forem tratados corretamente, o funcionamento geral do servidor HTTP pode ser interrompido. O router possui um callback para quando um callback de rota falha e impede a interrupção do serviço. Esse método só está acessível quando [ThrowExceptions](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ThrowExceptions.md) está definido como false. ```cs mainRouter.CallbackErrorHandler = (ex, context) => { return new HttpResponse(500) { Content = new StringContent($"Error: {ex.Message}") }; }; ``` --- # Manipulação de requisições Source: https://docs.sisk-framework.org/pt-br/docs/fundamentals/request-handlers.html Manipuladores de requisição, também conhecidos como "middlewares", são funções que são executadas antes ou depois que uma requisição é processada no roteador. Eles podem ser definidos por rota ou por roteador. Existem dois tipos de manipuladores de requisição: - **BeforeResponse**: define que o manipulador de requisição será executado antes de chamar a ação do roteador. - **AfterResponse**: define que o manipulador de requisição será executado após chamar a ação do roteador. Enviar uma resposta HTTP neste contexto sobrescreverá a resposta da ação do roteador. Ambos os manipuladores de requisição podem sobrescrever a resposta da função de callback real do roteador. Além disso, manipuladores de requisição podem ser úteis para validar uma requisição, como autenticação, conteúdo ou qualquer outra informação, como armazenar dados, logs ou outras etapas que podem ser realizadas antes ou depois de uma resposta. ![](https://docs.sisk-framework.org/assets/img/requesthandlers1.png) Dessa forma, um manipulador de requisição pode interromper toda essa execução e retornar uma resposta antes de concluir o ciclo, descartando todo o resto no processo. Exemplo: suponha que um manipulador de requisição de autenticação de usuário não o autentique. Ele impedirá que o ciclo de requisição continue e ficará pendente. Se isso acontecer no manipulador de requisição na posição dois, o terceiro e os subsequentes não serão avaliados. ![](https://docs.sisk-framework.org/assets/img/requesthandlers2.png) ## Criando um manipulador de requisição Para criar um manipulador de requisição, podemos criar uma classe que herda a interface [IRequestHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.IRequestHandler.md), no seguinte 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) { // Retornar null indica que o ciclo da requisição pode continuar return null; } else { // Retornar um objeto HttpResponse indica que esta resposta sobrescreverá respostas adjacentes. return new HttpResponse(System.Net.HttpStatusCode.Unauthorized); } } } ``` No exemplo acima, indicamos que se o cabeçalho `Authorization` estiver presente na requisição, ela deve continuar e o próximo manipulador de requisição ou o callback do roteador deve ser chamado, seja qual for o próximo. Se um manipulador de requisição for executado após a resposta por sua propriedade [ExecutionMode](https://docs.sisk-framework.org/api/Sisk.Core.Routing.IRequestHandler.ExecutionMode.md) e retornar um valor não nulo, ele sobrescreverá a resposta do roteador. Sempre que um manipulador de requisição retorna `null`, isso indica que a requisição deve continuar e o próximo objeto deve ser chamado ou o ciclo deve terminar com a resposta do roteador. Se você herdar da classe incorporada [RequestHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RequestHandler.md), pode retornar `Next()` para tornar essa intenção explícita: ```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 manipuladores que precisam de I/O, herde 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(); } } ``` Pequenos manipuladores inline também podem ser criados com `RequestHandler.Create` ou `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); }); ``` ## Associando um manipulador de requisição a uma única rota Você pode definir um ou mais manipuladores de requisição para uma rota. ```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 }); ``` Ou criando um 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); ``` ## Associando um manipulador de requisição a um roteador Você pode definir um manipulador de requisição global que será executado em todas as rotas de um roteador. ```cs {title="Router.cs"} mainRouter.GlobalRequestHandlers = new IRequestHandler[] { new AuthenticateUserRequestHandler() }; ``` ## Associando um manipulador de requisição a um atributo Você pode definir um manipulador de requisição em um atributo de método junto com um atributo de rota. ```cs {title="Controller/MyController.cs"} public class MyController { [RouteGet("/")] [RequestHandler] static HttpResponse Index(HttpRequest request) { return new HttpResponse() { Content = new StringContent("Hello world!") }; } } ``` Observe que é necessário passar o tipo desejado do manipulador de requisição e não uma instância de objeto. Dessa forma, o manipulador de requisição será instanciado pelo analisador do roteador. Você pode passar argumentos no construtor da classe com a propriedade [ConstructorArguments](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RequestHandlerAttribute.ConstructorArguments.md). Exemplo: ```cs {title="Controller/MyController.cs"} [RequestHandler("arg1", 123, ...)] public HttpResponse Index(HttpRequest request) { return res = new HttpResponse() { Content = new StringContent("Hello world!") }; } ``` Você também pode criar seu próprio atributo que implementa RequestHandler: ```cs {title="Middleware/Attributes/AuthenticateAttribute.cs"} public class AuthenticateAttribute : RequestHandlerAttribute { public AuthenticateAttribute() : base(typeof(AuthenticateUserRequestHandler), ConstructorArguments = new object?[] { "arg1", 123, ... }) { ; } } ``` E usá-lo assim: ```cs {title="Controller/MyController.cs"} [Authenticate] static HttpResponse Index(HttpRequest request) { return res = new HttpResponse() { Content = new StringContent("Hello world!") }; } ``` ## Ignorando um manipulador de requisição global Depois de definir um manipulador de requisição global em uma rota, você pode ignorar esse manipulador de requisição em rotas 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] > Se você estiver ignorando um manipulador de requisição, deve usar a mesma referência da instância criada anteriormente para pular. Criar outra instância de manipulador de requisição não ignorará o manipulador global, pois sua referência mudará. Lembre-se de usar a mesma referência de manipulador de requisição usada tanto em GlobalRequestHandlers quanto em BypassGlobalRequestHandlers. --- # Requisições Source: https://docs.sisk-framework.org/pt-br/docs/fundamentals/requests.html Requisições são estruturas que representam uma mensagem de requisição HTTP. O objeto [HttpRequest](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.md) contém funções úteis para manipular mensagens HTTP em toda a sua aplicação. Uma requisição HTTP é composta pelo método, caminho, versão, cabeçalhos e corpo. Neste documento, ensinaremos como obter cada um desses elementos. ## Obtendo o método da requisição Para obter o método da requisição recebida, você pode usar a propriedade Method: ```cs static HttpResponse Index(HttpRequest request) { HttpMethod requestMethod = request.Method; ... } ``` Esta propriedade retorna o método da requisição representado por um objeto [HttpMethod](https://learn.microsoft.com/pt-br/dotnet/api/system.net.http.httpmethod). > [!NOTE] > Ao contrário dos métodos de rota, esta propriedade não serve ao item [RouteMethod.Any](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouteMethod.md). Em vez disso, ela retorna o método real da requisição. ## Obtendo componentes da URL da requisição Você pode obter vários componentes de uma URL através de determinadas propriedades de uma requisição. Para este exemplo, vamos considerar a URL: ``` http://localhost:5000/user/login?email=foo@bar.com ``` | Nome do componente | Descrição | Valor do componente | | --- | --- | --- | | [Path](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Path.md) | Obtém o caminho da requisição. | `/user/login` | | [FullPath](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.FullPath.md) | Obtém o caminho da requisição e a string de consulta. | `/user/login?email=foo@bar.com` | | [FullUrl](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.FullUrl.md) | Obtém a string completa da URL da requisição. | `http://localhost:5000/user/login?email=foo@bar.com` | | [Host](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Host.md) | Obtém o host da requisição. | `localhost` | | [Authority](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Authority.md) | Obtém o host e a porta da requisição. | `localhost:5000` | | [QueryString](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.QueryString.md) | Obtém a consulta da requisição. | `?email=foo@bar.com` | | [Query](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Query.md) | Obtém a consulta da requisição em uma coleção de valores nomeados. | `{StringValueCollection object}` | | [IsSecure](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.IsSecure.md) | Determina se a requisição está usando SSL (true) ou não (false). | `false` | Você também pode optar por usar a propriedade [HttpRequest.Uri](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Uri.md), que inclui tudo acima em um único objeto. ## Metadados da requisição e cancelamento Sisk também anexa metadados operacionais a cada requisição. Essas propriedades são úteis para logs, rastreamento, localização, diagnósticos e operações de longa duração: | Propriedade ou método | Uso | | --- | --- | | [RequestId](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.RequestId.md) | Um identificador único para a requisição. Habilite [IncludeRequestIdHeader](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.IncludeRequestIdHeader.md) para retorná-lo como `X-Request-Id`. | | [RequestedAt](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.RequestedAt.md) | O momento em que o Sisk criou o objeto de requisição. | | [RemoteAddress](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.RemoteAddress.md) | O endereço do cliente resolvido a partir da conexão, ou do seu [ForwardingResolver](https://docs.sisk-framework.org/pt-br/docs/advanced/forwarding-resolvers.md). | | [Culture](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Culture.md) | A melhor cultura resolvida a partir de `Accept-Language`, recuando para a cultura atual. | | [DisconnectToken](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.DisconnectToken.md) | Um token de cancelamento sinalizado quando o cliente se desconecta, quando suportado pelo motor HTTP configurado. | | [Bag](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Bag.md) | Um armazenamento tipado de chave/valor compartilhado entre manipuladores de requisição e a ação da rota. | | [GetRawHttpRequest](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.GetRawHttpRequest.md) | Uma representação textual da requisição para diagnóstico. | ## Obtendo o corpo da requisição Algumas requisições incluem corpo, como formulários, arquivos ou transações de API. Você pode obter o corpo de uma requisição a partir da propriedade: ```cs // obtém o corpo da requisição como uma string, usando a codificação da requisição como codificador string body = request.Body; // ou obtém em um array de bytes byte[] bodyBytes = request.RawBody; // ou então, você pode transmiti-lo como stream. Stream requestStream = request.GetRequestStream(); // ou ler o corpo de forma assíncrona Memory bodyMemory = await request.GetBodyContentsAsync(); ``` Também é possível determinar se há um corpo na requisição e se ele está carregado com as propriedades [HasContents](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.HasContents.md), que determina se a requisição tem conteúdo, e [IsContentAvailable](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.IsContentAvailable.md) que indica que o servidor HTTP recebeu totalmente o conteúdo do ponto remoto. Não é possível ler o conteúdo da requisição através de `GetRequestStream` mais de uma vez. Se você ler com este método, os valores em `RawBody` e `Body` também não ficarão disponíveis. Não é necessário descartar o stream da requisição no contexto da requisição, pois ele é descartado ao final da sessão HTTP em que foi criado. Além disso, você pode usar a propriedade [HttpRequest.RequestEncoding](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.RequestEncoding.md) para obter a melhor codificação para decodificar a requisição manualmente. O servidor tem limites para leitura do conteúdo da requisição, que se aplicam tanto a [HttpRequest.Body](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Body.md) quanto a [HttpRequest.RawBody](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Body.md). Essas propriedades copiam todo o stream de entrada para um buffer local do mesmo tamanho de [HttpRequest.ContentLength](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.ContentLength.md). Uma resposta com status 413 Content Too Large é retornada ao cliente se o conteúdo enviado for maior que [HttpServerConfiguration.MaximumContentLength](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.MaximumContentLength.md) definido na configuração do usuário. Além disso, se não houver limite configurado ou se ele for muito grande, o servidor lançará uma [OutOfMemoryException](https://learn.microsoft.com/en-us/dotnet/api/system.outofmemoryexception?view=net-8.0) quando o conteúdo enviado pelo cliente exceder [Int32.MaxValue](https://learn.microsoft.com/en-us/dotnet/api/system.int32.maxvalue) (2 GB) e se o conteúdo for tentado ser acessado através de uma das propriedades mencionadas acima. Você ainda pode lidar com o conteúdo por streaming. > [!NOTE] > Embora o Sisk permita, é sempre uma boa ideia seguir a Semântica HTTP ao criar sua aplicação e não obter ou servir conteúdo em métodos que não o permitem. Leia sobre [RFC 9110 "HTTP Semantics"](https://httpwg.org/spec/rfc9110.html). ## Lendo requisições JSON Para APIs JSON, prefira os auxiliares JSON embutidos em vez de ler `Body` e desserializar manualmente. Eles utilizam [System.Text.Json](https://learn.microsoft.com/en-us/dotnet/api/system.text.json) e, por padrão, [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 a sobrecarga assíncrona quando você já está em uma rota async ou deseja que o cancelamento da requisição interrompa a desserialização: ```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); }); ``` Você pode passar opções personalizadas de [JsonSerializerOptions](https://learn.microsoft.com/en-us/dotnet/api/system.text.json.jsonserializeroptions) para um endpoint específico: ```cs var options = new JsonSerializerOptions(JsonSerializerDefaults.Web) { PropertyNameCaseInsensitive = true }; UserDto? user = request.GetJsonContent(options); ``` Para aplicações Native AOT ou sensíveis a trimming, use a sobrecarga `JsonTypeInfo` gerada por um `JsonSerializerContext`: ```cs [JsonSerializable(typeof(CreateUserRequest))] public partial class AppJsonSerializerContext : JsonSerializerContext { } CreateUserRequest? body = await request.GetJsonContentAsync( AppJsonSerializerContext.Default.CreateUserRequest, request.DisconnectToken); ``` A mesma regra de leitura única se aplica aos auxiliares JSON: depois que o Sisk lê o stream da requisição através de `GetJsonContent`, `GetJsonContentAsync`, `Body` ou `RawBody`, você não pode consumir novamente o mesmo corpo via `GetRequestStream()`. ## Obtendo o contexto da requisição O HTTP Context é um objeto exclusivo do Sisk que armazena informações do servidor HTTP, rota, roteador e manipulador de requisição. Você pode usá-lo para organizar-se em um ambiente onde esses objetos são difíceis de organizar. Você pode obter o [HttpContext](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpContext.md) em execução usando o método estático `HttpContext.GetCurrentContext()`. Este método retorna o contexto da requisição que está sendo processada na thread atual. ```cs HttpContext context = HttpContext.GetCurrentContext(); ``` ### Modo de Log A propriedade [HttpContext.LogMode](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpContext.LogMode.md) permite controlar o comportamento de logging para a requisição atual. Você pode habilitar ou desabilitar o logging para requisições específicas, sobrescrevendo a configuração padrão do servidor. ```cs // Desabilitar logging para esta requisição context.LogMode = LogOutputMode.None; ``` ### Request Bag O objeto [RequestBag](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpContext.RequestBag.md) contém informações armazenadas que são passadas de um manipulador de requisição para outro ponto, e podem ser consumidas no destino final. Esse objeto também pode ser usado por manipuladores de requisição que são executados após o callback da rota. > [!TIP] > Esta propriedade também está acessível pela propriedade [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); } } } ``` O manipulador acima definirá `AuthenticatedUser` no request bag, e poderá ser consumido posteriormente no 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}!") }; } } ``` Você também pode usar os métodos auxiliares `Bag.Set()` e `Bag.Get()` para obter ou definir objetos pelos seus tipos singleton. A classe `TypedValueDictionary` também fornece os métodos `GetValue` e `SetValue` para maior controle. ```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(); ... } ``` ## Obtendo dados de formulário Você pode obter valores de dados de formulário em uma [StringKeyStoreCollection](https://docs.sisk-framework.org/api/Sisk.Core.Entity.StringKeyStoreCollection.md) com o exemplo abaixo: ```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)) { ... } } ``` A versão assíncrona é útil quando o corpo da requisição pode ser grande ou quando você deseja suporte a cancelamento: ```cs var form = await request.GetFormContentAsync(request.DisconnectToken); ``` ## Obtendo dados de formulário multipart O HTTP request do Sisk permite obter conteúdos multipart enviados, como arquivos, campos de formulário ou qualquer conteúdo binário. ```cs {title="Controller/Auth.cs"} [RoutePost("/upload-contents")] public HttpResponse Index(HttpRequest request) { // o método a seguir lê todo o input da requisição em // um array de MultipartObjects var multipartFormDataObjects = request.GetMultipartFormContent(); foreach (MultipartObject uploadedObject in multipartFormDataObjects) { // O nome do arquivo fornecido pelo multipart form data. // Null é retornado se o objeto não for um arquivo. Console.WriteLine("File name : " + uploadedObject.Filename); // O nome do campo do multipart form data. Console.WriteLine("Field name : " + uploadedObject.Name); // O tamanho do conteúdo do multipart form data. Console.WriteLine("Content length : " + uploadedObject.ContentLength); // Determina o formato da imagem baseado no cabeçalho do arquivo para cada // tipo de conteúdo conhecido. Se o conteúdo não for um formato de arquivo // comum reconhecido, este método abaixo retornará MultipartObjectCommonFormat.Unknown Console.WriteLine("Common format : " + uploadedObject.GetCommonFileFormat()); } } ``` Use [GetMultipartFormContentAsync](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.GetMultipartFormContentAsync.md) quando a rota for assíncrona: ```cs var multipartFormDataObjects = await request.GetMultipartFormContentAsync(request.DisconnectToken); ``` Você pode ler mais sobre os [objetos multipart do Sisk](https://docs.sisk-framework.org/api/Sisk.Core.Entity.MultipartObject.md) e seus métodos, propriedades e funcionalidades. ## Detectando desconexão do cliente Desde a versão v1.15 do Sisk, o framework fornece um token de cancelamento através de [HttpRequest.DisconnectToken](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.DisconnectToken.md). Quando o motor HTTP configurado suporta detecção de desconexão, esse token é cancelado quando a conexão do cliente é fechada antes que a resposta seja concluída. Isso é útil para interromper operações de longa duração quando o cliente não está mais aguardando o resultado. ```csharp router.MapGet("/connect", async (HttpRequest req) => { // obtém o token de desconexão da requisição var dc = req.DisconnectToken; await LongOperationAsync(dc); return new HttpResponse(); }); ``` Esse token não é compatível com todos os motores HTTP, e cada um requer uma implementação. O motor padrão do Sisk, baseado em `System.Net.HttpListener`, não suporta detecção de desconexão do cliente. Quando sua aplicação usa o motor padrão, `DisconnectToken` é `CancellationToken.None`; na prática, ele é um token que não pode ser cancelado e deve ser tratado como indisponível. O [motor Cadente](https://docs.sisk-framework.org/pt-br/docs/cadente.md) suporta `DisconnectToken`. Se sua rota depende de cancelamento consciente de desconexão, use o Cadente ou outro motor que implemente explicitamente esse comportamento. Mesmo com um motor suportado, o cancelamento é cooperativo: passe o token para APIs assíncronas e verifique-o em seu próprio trabalho de longa duração. ## Suporte a eventos enviados pelo servidor Sisk suporta [Server-sent events](https://developer.mozilla.org/en-US/docs/pt-br/Web/API/Server-sent_events), que permite enviar blocos como um stream e manter a conexão entre o servidor e o cliente viva. Chamar o método [HttpRequest.GetEventSource](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.GetEventSource.md) colocará o HttpRequest em seu estado de listener. A partir disso, o contexto desta requisição HTTP não esperará um HttpResponse, pois ele sobreporá os pacotes enviados pelos eventos do lado do servidor. Após enviar todos os pacotes, o callback deve retornar o método [Close](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequestEventSource.Close.md), que enviará a resposta final ao cliente e indicará que o streaming terminou. Não é possível prever qual será o comprimento total de todos os pacotes que serão enviados, portanto não é possível determinar o fim da conexão com o cabeçalho `Content-Length`. Pela maioria dos navegadores, eventos do lado do servidor não suportam o envio de cabeçalhos HTTP ou métodos diferentes de GET. Portanto, tenha cuidado ao usar manipuladores de requisição com solicitações de event‑source que exigem cabeçalhos específicos, pois provavelmente eles não estarão presentes. Além disso, a maioria dos navegadores reinicia streams se o método [EventSource.close](https://developer.mozilla.org/en-US/docs/pt-br/Web/API/EventSource/close) não for chamado no cliente após receber todos os pacotes, causando processamento adicional infinito no lado do servidor. Para evitar esse tipo de problema, é comum enviar um pacote final indicando que a fonte de eventos terminou de enviar todos os pacotes. O exemplo abaixo mostra como o navegador pode se comunicar com o servidor que suporta eventos do lado do servidor. ```html {title="sse-example.html"} Fruits:
    ``` E enviar progressivamente as mensagens ao 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(); } } ``` Ao executar este código, esperamos um resultado semelhante a este: ## Resolvendo IPs e hosts proxyados Sisk pode ser usado com proxies, e portanto endereços IP podem ser substituídos pelo endpoint do proxy na transação de um cliente para o proxy. Você pode definir seus próprios resolvedores no Sisk com [forwarding resolvers](https://docs.sisk-framework.org/pt-br/docs/advanced/forwarding-resolvers.md). ## Codificação de cabeçalhos A codificação de cabeçalhos pode ser um problema para algumas implementações. No Windows, cabeçalhos UTF‑8 não são suportados, então ASCII é usado. O Sisk possui um conversor de codificação embutido, que pode ser útil para decodificar cabeçalhos codificados incorretamente. Essa operação é custosa e está desabilitada por padrão, mas pode ser habilitada com [HttpServerConfiguration.NormalizeHeadersEncodings](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.NormalizeHeadersEncodings.md). --- # Respostas Source: https://docs.sisk-framework.org/pt-br/docs/fundamentals/responses.html Respostas representam objetos que são respostas HTTP a requisições HTTP. Elas são enviadas pelo servidor ao cliente como uma indicação da solicitação de um recurso, página, documento, arquivo ou outro objeto. Uma resposta HTTP é composta por status, cabeçalhos e conteúdo. Neste documento, ensinaremos como arquitetar respostas HTTP com o Sisk. ## Definindo um status HTTP A lista de status HTTP é a mesma desde o HTTP/1.0, e o Sisk suporta todos eles. ```cs HttpResponse res = new HttpResponse(); res.Status = System.Net.HttpStatusCode.Accepted; // 202 ``` Ou com Sintaxe Fluent: ```cs new HttpResponse() .WithStatus(200) // or .WithStatus(HttpStatusCode.Ok) // or .WithStatus(HttpStatusInformation.Ok); ``` Você pode ver a lista completa de HttpStatusCode disponíveis [aqui](https://learn.microsoft.com/pt-br/dotnet/api/system.net.httpstatuscode). Você também pode fornecer seu próprio código de status usando a estrutura [HttpStatusInformation](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpStatusInformation.md). ## Corpo e content-type Sisk suporta objetos de conteúdo nativos do .NET para enviar corpo nas respostas. Você pode usar a classe [StringContent](https://learn.microsoft.com/pt-br/dotnet/api/system.net.http.stringcontent) para enviar uma resposta JSON, por exemplo: ```cs HttpResponse res = new HttpResponse(); res.Content = new StringContent(myJson, Encoding.UTF8, "application/json"); ``` O servidor sempre tentará calcular o `Content-Length` a partir do que você definiu no conteúdo se você não o definiu explicitamente em um cabeçalho. Se o servidor não conseguir obter implicitamente o cabeçalho Content-Length do conteúdo da resposta, a resposta será enviada com Chunked-Encoding. Você também pode transmitir a resposta enviando um [StreamContent](https://learn.microsoft.com/pt-br/dotnet/api/system.net.http.streamcontent) ou usando o método [GetResponseStream](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.GetResponseStream.md). ## Cabeçalhos de resposta Você pode adicionar, editar ou remover cabeçalhos que está enviando na resposta. O exemplo abaixo mostra como enviar uma resposta de redirecionamento ao cliente. ```cs HttpResponse res = new HttpResponse(); res.Status = HttpStatusCode.Moved; res.Headers.Add(HttpKnownHeaderNames.Location, "/login"); ``` Ou com Sintaxe Fluent: ```cs new HttpResponse(301) .WithHeader("Location", "/login"); ``` Quando você usa o método [Add](https://docs.sisk-framework.org/api/Sisk.Core.Entity.HttpHeaderCollection.Add.md) de HttpHeaderCollection, está adicionando um cabeçalho à requisição sem alterar os que já foram enviados. O método [Set](https://docs.sisk-framework.org/api/Sisk.Core.Entity.HttpHeaderCollection.Set.md) substitui os cabeçalhos com o mesmo nome pelo valor indicado. O indexador de HttpHeaderCollection chama internamente o método Set para substituir os cabeçalhos. Você também pode recuperar valores de cabeçalhos usando o método [GetHeaderValue](https://docs.sisk-framework.org/api/Sisk.Core.Entity.HttpHeaderCollection.GetHeaderValue.md). Esse método ajuda a obter valores tanto dos cabeçalhos da resposta quanto dos cabeçalhos de conteúdo (se houver conteúdo definido). ```cs // Retorna o valor do cabeçalho "Content-Type", verificando tanto response.Headers quanto response.Content.Headers string? contentType = response.GetHeaderValue("Content-Type"); ``` ## Enviando cookies O Sisk possui métodos que facilitam a definição de cookies no cliente. Cookies definidos por este método já são codificados em URL e atendem ao padrão RFC-6265. ```cs HttpResponse res = new HttpResponse(); res.SetCookie("cookie-name", "cookie-value"); ``` Ou com Sintaxe Fluent: ```cs new HttpResponse(301) .WithCookie("cookie-name", "cookie-value", expiresAt: DateTime.Now.Add(TimeSpan.FromDays(7))); ``` Existem outras [versões mais completas](https://docs.sisk-framework.org/api/Sisk.Core.Helpers.CookieHelper.SetCookie.md) do mesmo método. ## Respostas em chunked Você pode definir o transfer encoding como chunked para enviar respostas grandes. ```cs HttpResponse res = new HttpResponse(); res.SendChunked = true; ``` Ao usar chunked-encoding, o cabeçalho Content-Length é omitido automaticamente. ## Stream de resposta Streams de resposta são uma forma gerenciada que permite enviar respostas de maneira segmentada. É uma operação de nível mais baixo que usar objetos HttpResponse, pois requer que você envie os cabeçalhos e o conteúdo manualmente, e então feche a conexão. Este exemplo abre um stream somente leitura para o arquivo, copia o stream para o stream de saída da resposta e não carrega o arquivo inteiro na memória. Isso pode ser útil para servir arquivos médios ou grandes. ```cs // obtém o stream de saída da resposta using var fileStream = File.OpenRead("my-big-file.zip"); var responseStream = request.GetResponseStream(); // define a codificação da resposta para usar chunked-encoding // também você não deve enviar o cabeçalho content-length ao usar // chunked encoding responseStream.SendChunked = true; responseStream.SetStatus(200); responseStream.SetHeader(HttpKnownHeaderNames.ContentType, contentType); // copia o stream do arquivo para o stream de saída da resposta fileStream.CopyTo(responseStream.ResponseStream); // fecha o stream return responseStream.Close(); ``` ## Compressão GZip, Deflate e Brotli Você pode enviar respostas com conteúdo comprimido no Sisk comprimindo conteúdos HTTP. Primeiro, encapsule seu [HttpContent](https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpcontent) em um dos compressores abaixo para enviar a resposta comprimida ao cliente. ```cs router.MapGet("/hello.html", request => { string myHtml = "..."; return new HttpResponse () { Content = new GZipContent(new HtmlContent(myHtml)), // ou Content = new BrotliContent(new HtmlContent(myHtml)), // ou Content = new DeflateContent(new HtmlContent(myHtml)), }; }); ``` Você também pode usar esses conteúdos comprimidos com streams. ```cs router.MapGet("/archive.zip", request => { // não aplique "using" aqui. o HttpServer descartará seu conteúdo // após enviar a resposta. var archive = File.OpenRead("/path/to/big-file.zip"); return new HttpResponse () { Content = new GZipContent(archive) } }); ``` Os cabeçalhos Content-Encoding são definidos automaticamente ao usar esses conteúdos. ## Compressão automática É possível comprimir automaticamente respostas HTTP com a propriedade [EnableAutomaticResponseCompression](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.EnableAutomaticResponseCompression.md). Essa propriedade encapsula automaticamente o conteúdo da resposta do roteador em um conteúdo compressível que é aceito pela requisição, desde que a resposta não herde de um [CompressedContent](https://docs.sisk-framework.org/api/Sisk.Core.Http.CompressedContent.md). Apenas um conteúdo compressível é escolhido para uma requisição, escolhido de acordo com o cabeçalho Accept-Encoding, que segue a ordem: - [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) Se a requisição especificar que aceita qualquer um desses métodos de compressão, a resposta será comprimida automaticamente. ## Tipos de resposta implícitos Você pode usar outros tipos de retorno além de HttpResponse, mas é necessário configurar o roteador sobre como ele lidará com cada tipo de objeto. O conceito é sempre retornar um tipo de referência e transformá-lo em um objeto HttpResponse válido. Rotas que retornam HttpResponse não passam por nenhuma conversão. Tipos de valor (structures) não podem ser usados como tipo de retorno porque não são compatíveis com o [RouterCallback](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouterCallback.md), portanto devem ser encapsulados em um ValueResult para poderem ser usados em manipuladores. Considere o exemplo a seguir de um módulo de roteador que não usa HttpResponse no 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; } } ``` Com isso, agora é necessário definir no roteador como ele lidará com cada tipo de objeto. Objetos são sempre o primeiro argumento do manipulador e o tipo de saída deve ser um HttpResponse válido. Além disso, os objetos de saída de uma rota nunca devem ser nulos. Para tipos ValueResult não é necessário indicar que o objeto de entrada é um ValueResult e apenas T, já que ValueResult é um objeto refletido de seu componente original. A associação de tipos não compara o que foi registrado com o tipo do objeto retornado do callback do roteador. Em vez disso, verifica se o tipo do resultado do roteador é atribuível ao tipo registrado. Registrar um manipulador do tipo Object será um fallback para todos os tipos previamente não validados. A ordem de inserção dos manipuladores de valor também importa, portanto registrar um manipulador Object ignorará todos os outros manipuladores específicos de tipo. Sempre registre manipuladores de valor específicos primeiro para garantir a ordem. ```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)); }); // registering an value handler of object must be the last // value handler which will be used as an fallback r.RegisterValueHandler(fallback => { return new HttpResponse() { Status = HttpStatusCode.OK, Content = JsonContent.Create(fallback) }; }); ``` ## Ações Diferidas Quando uma requisição chega ao roteador, ela primeiro passa pelos [request handlers](https://docs.sisk-framework.org/pt-br/docs/fundamentals/request-handlers.md), é processada na ação do roteador e depois pelos manipuladores de requisição pós-execução. O resultado da ação do roteador é o que é passado para os manipuladores de valor, e o resultado do manipulador de valor é o que é enviado ao cliente como resposta. Esse ciclo de vida ocorre dentro de um contexto assíncrono. Esse contexto assíncrono expõe variáveis que o usuário pode adicionar ao [HttpContext Bag](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpContext.md) para compartilhar dados entre manipuladores e a ação do roteador. O valor retornado pela ação do roteador é adicionado a esse contexto assíncrono e pode ser acessado pelos manipuladores de valor. Ações diferidas são ações que sempre serão executadas ao final do ciclo, após entregar a resposta ao cliente, mas ainda dentro do mesmo contexto assíncrono. Essas ações podem ser usadas para executar tarefas de longa duração que não precisam ser concluídas para enviar uma resposta ao cliente, como salvar logs, atualizar o banco de dados, enviar e‑mails, etc. Exceções ainda são capturadas em ações diferidas e serão tratadas da mesma forma que uma exceção lançada em qualquer ponto do ciclo de vida da requisição. A diferença é que o cliente já terá uma resposta, portanto a exceção é tratada pelo tratamento de erro padrão. Adie a execução de uma ação usando o método [HttpContext.EnqueueDeferredAction](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpContext.EnqueueDeferredAction.md). O método recebe uma função assíncrona que representa a ação a ser executada e um timeout opcional para limitar o tempo de execução da ação. Se a ação não for concluída dentro do limite de tempo, ela 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."); } // agenda uma ação de longa duração que será executada após enviar a resposta ao cliente, mas ainda dentro do mesmo contexto assíncrono da requisição 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...") }; } ``` ## Observação sobre objetos enumeráveis e arrays Objetos de resposta implícitos que implementam [IEnumerable](https://learn.microsoft.com/pt-br/dotnet/api/system.collections.ienumerable?view=net-8.0) são lidos na memória através do método `ToArray()` antes de serem convertidos por um manipulador de valor definido. Para que isso ocorra, o objeto `IEnumerable` é convertido em um array de objetos, e o conversor de resposta sempre receberá um `Object[]` em vez do tipo original. Considere o seguinte cenário: ```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(); ``` No exemplo acima, o conversor `IEnumerable` **nunca será chamado**, porque o objeto de entrada será sempre um `Object[]` e não é convertível para `IEnumerable`. No entanto, o conversor abaixo que recebe um `IEnumerable` receberá sua entrada, já que seu valor é compatível. Se você precisar realmente lidar com o tipo do objeto que será enumerado, precisará usar reflexão para obter o tipo do elemento da coleção. Todos os objetos enumeráveis (listas, arrays e coleções) são convertidos em um array de objetos pelo conversor de resposta HTTP. Valores que implementam [IAsyncEnumerable](https://learn.microsoft.com/pt-br/dotnet/api/system.collections.generic.iasyncenumerable-1?view=net-8.0) são tratados automaticamente pelo servidor se a propriedade [ConvertIAsyncEnumerableIntoEnumerable](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ConvertIAsyncEnumerableIntoEnumerable.md) estiver habilitada, similar ao que acontece com `IEnumerable`. Essa opção está habilitada por padrão em `HttpServerConfiguration`; uma enumeração assíncrona é convertida em um enumerador bloqueante e então convertida em um array síncrono de objetos. Desative-a somente quando você fornecer seu próprio manipulador de valor ou estratégia de resposta em streaming para sequências assíncronas. --- # Registro de logs Source: https://docs.sisk-framework.org/pt-br/docs/features/logging.html Você pode configurar o Sisk para gravar logs de acesso e de erro automaticamente. É possível definir rotação de logs, extensões e frequência. A classe [LogStream](https://docs.sisk-framework.org/api/Sisk.Core.Http.LogStream.md) fornece uma maneira assíncrona de escrever logs e mantê‑los em uma fila de escrita aguardável. A classe `LogStream` implementa `IAsyncDisposable`, garantindo que todos os logs pendentes sejam gravados antes que o stream seja fechado. Neste artigo mostraremos como configurar o registro de logs para sua aplicação. ## Logs de acesso baseados em arquivo Logs para arquivos abrem o arquivo, escrevem a linha de texto e então fecham o arquivo para cada linha escrita. Esse procedimento foi adotado para manter a responsividade de escrita nos 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(); } } ``` O código acima gravará todas as requisições recebidas no arquivo `logs/access.log`. Observe que o arquivo é criado automaticamente se não existir, porém a pasta anterior não é. Não é necessário criar o diretório `logs/` pois a classe LogStream o cria automaticamente. ## Registro de logs baseado em stream Você pode gravar arquivos de log em instâncias de objetos `TextWriter`, como `Console.Out`, passando um objeto `TextWriter` no construtor: ```cs {title="Program.cs"} using var app = HttpServer.CreateBuilder() .UseConfiguration(config => { config.AccessLogsStream = new LogStream(Console.Out); }) .Build(); ``` Para cada mensagem gravada no log baseado em stream, o método `TextWriter.Flush()` é chamado. ## Formatação do log de acesso Você pode personalizar o formato do log de acesso por variáveis predefinidas. Considere a linha a seguir: ```cs config.AccessLogsFormat = "%dd/%dmm/%dy %tH:%ti:%ts %tz %ls %ri %rs://%ra%rz%rq [%sc %sd] %lin -> %lou in %lmsms [%{user-agent}]"; ``` Ela gravará uma mensagem 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] Você pode formatar seu arquivo de log conforme a tabela descrita abaixo: | Valor | O que representa | Exemplo | |-------|-------------------|---------| | %dd | Dia do mês (formatado com dois dígitos) | 05 | | %dmmm | Nome completo do mês | Julho | | %dmm | Nome abreviado do mês (três letras) | Jul | | %dm | Número do mês (formatado com dois dígitos) | 07 | | %dy | Ano (formatado com quatro dígitos) | 2023 | | %th | Hora no formato de 12 horas | 03 | | %tH | Hora no formato de 24 horas (HH) | 15 | | %ti | Minutos (formatado com dois dígitos) | 30 | | %ts | Segundos (formatado com dois dígitos) | 45 | | %tm | Milissegundos (formatado com três dígitos) | 123 | | %tz | Deslocamento de fuso horário (horas totais em UTC) | +03:00 | | %ri | Endereço IP remoto do cliente | 192.168.1.100 | | %rm | Método HTTP (maiúsculas) | GET | | %rs | Esquema da URI (http/https) | https | | %ra | Autoridade da URI (domínio) | example.com | | %rh | Host da requisição | www.example.com | | %rp | Porta da requisição | 443 | | %rz | Caminho da requisição | /path/to/resource | | %rq | String de consulta | ?key=value&another=123 | | %sc | Código de status da resposta HTTP | 200 | | %sd | Descrição do status da resposta HTTP | OK | | %lin | Tamanho da requisição legível por humanos | 1.2 KB | | %linr | Tamanho bruto da requisição (bytes) | 1234 | | %lou | Tamanho da resposta legível por humanos | 2.5 KB | | %lour | Tamanho bruto da resposta (bytes) | 2560 | | %lms | Tempo decorrido em milissegundos | 120 | | %ls | Status de execução | Executado | | %{header-name} | Representa o cabeçalho `header-name` da requisição. | `Mozilla/5.0 (platform; rv:gecko [...]` | | %{:header-name} | Representa o cabeçalho `header-name` da resposta. | `application/json` | Você também pode usar `HttpServerConfiguration.DefaultAccessLogFormat` para utilizar o formato padrão de log de acesso. ## Rotação de logs Você pode configurar o servidor HTTP para rotacionar os arquivos de log para um arquivo comprimido .gz quando eles atingirem determinado tamanho. O tamanho é verificado periodicamente pelo limiar que você definir. ```cs LogStream errorLog = new LogStream("logs/error.log") .ConfigureRotatingPolicy( maximumSize: 64 * SizeHelper.UnitMb, dueTime: TimeSpan.FromHours(6)); ``` O código acima verificará a cada seis horas se o arquivo do LogStream atingiu o limite de 64 MB. Caso positivo, o arquivo será comprimido para um .gz e então `access.log` será limpo. Durante esse processo, a escrita no arquivo fica bloqueada até que a compressão e limpeza terminem. Todas as linhas que chegarem para ser escritas nesse período ficarão em uma fila aguardando o fim da compressão. Esta função funciona apenas com LogStreams baseados em arquivo. ## Registro de erros Quando o servidor não lança erros para o depurador, ele encaminha os erros para gravação de log quando houver algum. Você pode configurar a gravação de erros com: ```cs config.ThrowExceptions = false; config.ErrorsLogsStream = new LogStream("error.log"); ``` Esta propriedade gravará algo no log somente se o erro não for capturado pelo callback ou pela propriedade [Router.CallbackErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.CallbackErrorHandler.md). O erro gravado pelo servidor sempre inclui a data e hora, os cabeçalhos da requisição (não o corpo), o rastreamento do erro e o rastreamento da exceção interna, se houver. ## Outras instâncias de registro Sua aplicação pode ter zero ou múltiplos LogStreams, não há limite para a quantidade de canais de log que ela pode ter. Portanto, é possível direcionar o log da sua aplicação para um arquivo diferente do AccessLog ou ErrorLog padrão. ```cs LogStream appMessages = new LogStream("messages.log"); appMessages.WriteLine("Application started at {0}", DateTime.Now); ``` ## Estendendo LogStream Você pode estender a classe `LogStream` para gravar formatos personalizados, compatíveis com o mecanismo de logs atual do Sisk. O exemplo abaixo permite escrever mensagens coloridas no Console através da biblioteca Spectre.Console: ```cs {title="CustomLogStream.cs"} public class CustomLogStream : LogStream { protected override void WriteLineInternal(string line) { base.WriteLineInternal($"[{DateTime.Now:g}] {line}"); } } ``` Outra forma de gravar automaticamente logs personalizados para cada requisição/resposta é criar um [HttpServerHandler](https://docs.sisk-framework.org/api/Sisk.Core.Http.Handlers.HttpServerHandler.md). O exemplo abaixo é um pouco mais completo. Ele grava o corpo da requisição e da resposta em JSON no Console. Pode ser útil para depurar requisições em geral. Este exemplo faz uso de ContextBag e 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) { // Neste ponto, a conexão está aberta e o cliente enviou o cabeçalho especificando // que o conteúdo é JSON. A linha abaixo lê o conteúdo e o deixa armazenado na requisição. // // Se o conteúdo não for lido na ação da requisição, o GC provavelmente coletará o conteúdo // após o envio da resposta ao cliente, portanto o conteúdo pode não estar disponível após a resposta ser fechada. // _ = request.RawBody; // adiciona uma dica no contexto para indicar que esta requisição possui um corpo 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")) { // reformata o JSON usando a 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 se a resposta é 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 { // obtém o status interno de manipulação do 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 pelo Servidor Source: https://docs.sisk-framework.org/pt-br/docs/features/server-sent-events.html O Sisk oferece suporte ao envio de mensagens através de Server Sent Events nativamente. Você pode criar conexões descartáveis e persistentes, obter as conexões em tempo de execução e utilizá‑las. Esse recurso tem algumas limitações impostas pelos navegadores, como o envio apenas de mensagens de texto e a impossibilidade de fechar permanentemente uma conexão. Uma conexão fechada do lado do servidor fará com que o cliente tente reconectar periodicamente a cada 5 segundos (3 em alguns navegadores). Essas conexões são úteis para enviar eventos do servidor ao cliente sem que o cliente precise solicitar a informação a cada vez. ## Criando uma conexão SSE Uma conexão SSE funciona como uma requisição HTTP normal, mas ao invés de enviar uma resposta e fechar a conexão imediatamente, a conexão permanece aberta para enviar mensagens. Ao chamar o método [HttpRequest.GetEventSource()](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.GetEventSource.md), a requisição fica em estado de espera enquanto a instância SSE é criada. ```cs r.MapGet("/", (req) => { using var sse = req.GetEventSource(); sse.Send("Hello, world!"); return sse.Close(); }); ``` No código acima, criamos uma conexão SSE e enviamos a mensagem "Hello, world", em seguida fechamos a conexão SSE do lado do servidor. > [!NOTE] > Ao fechar uma conexão do lado do servidor, por padrão o cliente tentará se conectar novamente naquele ponto e a conexão será reiniciada, executando o método novamente, indefinidamente. > > É comum encaminhar uma mensagem de término do servidor sempre que a conexão for fechada pelo servidor para impedir que o cliente tente reconectar novamente. ## Anexando cabeçalhos Se precisar enviar cabeçalhos, você pode usar o método [HttpRequestEventSource.AppendHeader](https://docs.sisk-framework.org/api/Sisk.Core.Http.Streams.HttpRequestEventSource.AppendHeader.md) antes de enviar quaisquer mensagens. ```cs r.MapGet("/", (req) => { using var sse = req.GetEventSource(); sse.AppendHeader("Header-Key", "Header-value"); sse.Send("Hello!"); return sse.Close(); }); ``` Observe que é necessário enviar os cabeçalhos antes de enviar quaisquer mensagens. ## Conexões Wait-For-Fail As conexões são normalmente terminadas quando o servidor não consegue mais enviar mensagens devido a uma possível desconexão do cliente. Com isso, a conexão é encerrada automaticamente e a instância da classe é descartada. Mesmo com uma reconexão, a instância da classe não funcionará, pois está vinculada à conexão anterior. Em algumas situações, você pode precisar dessa conexão mais tarde e não quer gerenciá‑la via método de callback da rota. Para isso, podemos identificar as conexões SSE com um identificador e obtê‑las posteriormente usando‑o, mesmo fora do callback da rota. Além disso, marcamos a conexão com [WaitForFail](https://docs.sisk-framework.org/api/Sisk.Core.Http.Streams.HttpRequestEventSource.WaitForFail.md) para que a rota não seja terminada e a conexão seja encerrada automaticamente. Uma conexão SSE em `WaitForFail` aguarda um erro de envio causado por desconexão, ou que a tolerância de ociosidade configurada expire, antes que a rota retome e feche a conexão. ```cs r.MapGet("/", (req) => { using var sse = req.GetEventSource("my-index-connection"); sse.WaitForFail(TimeSpan.FromSeconds(15)); // wait for 15 seconds without any message before terminating the connection return sse.Close(); }); ``` O método acima criará a conexão, a manipulará e aguardará uma desconexão ou erro. ```cs HttpRequestEventSource? evs = server.EventSources.GetByIdentifier("my-index-connection"); if (evs != null) { // the connection is still alive evs.Send("Hello again!"); } ``` E o trecho acima tentará localizar a conexão recém‑criada e, se existir, enviará uma mensagem para ela. Todas as conexões de servidor ativas que forem identificadas ficarão disponíveis na coleção [HttpServer.EventSources](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServer.EventSources.md). Essa coleção armazena apenas conexões ativas e identificadas. Conexões fechadas são removidas da coleção. > [!NOTE] > É importante observar que o keep‑alive tem um limite estabelecido por componentes que podem estar conectados ao Sisk de forma incontrolável, como um proxy web, um kernel HTTP ou um driver de rede, e eles fecham conexões ociosas após um determinado período de tempo. > > Portanto, é importante manter a conexão aberta enviando pings periódicos ou estendendo o tempo máximo antes que a conexão seja fechada. Leia a próxima seção para entender melhor o envio de pings periódicos. ## Configurando política de ping de conexões A Política de Ping é uma forma automatizada de enviar mensagens periódicas ao seu cliente. Essa função permite que o servidor detecte quando o cliente se desconectou daquela conexão sem precisar mantê‑la aberta 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(); } ``` No código acima, a cada 5 segundos, uma nova mensagem de ping será enviada ao cliente. Isso manterá a conexão TCP viva e impedirá que ela seja fechada por inatividade. Além disso, quando uma mensagem falha ao ser enviada, a conexão é fechada automaticamente, liberando os recursos usados pela conexão. Use [SendAsync](https://docs.sisk-framework.org/api/Sisk.Core.Http.Streams.HttpRequestEventSource.SendAsync.md) e [CloseAsync](https://docs.sisk-framework.org/api/Sisk.Core.Http.Streams.HttpRequestEventSource.CloseAsync.md) em rotas assíncronas. Se precisar descartar eventos enfileirados antes de fechar, chame [Cancel](https://docs.sisk-framework.org/api/Sisk.Core.Http.Streams.HttpRequestEventSource.Cancel.md). ## Consultando conexões Você pode buscar conexões ativas usando um predicado sobre o identificador da conexão, para poder fazer broadcast, por exemplo. ```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-'"); } ``` Você também pode usar o método [All](https://docs.sisk-framework.org/api/Sisk.Core.Http.Streams.HttpEventSourceCollection.All.md) para obter todas as conexões SSE ativas. --- # Web Sockets Source: https://docs.sisk-framework.org/pt-br/docs/features/websockets.html Sisk também oferece suporte a web sockets, permitindo receber e enviar mensagens ao cliente. Esse recurso funciona bem na maioria dos navegadores, mas no Sisk ainda está experimental. Por favor, se encontrar algum bug, reporte no GitHub. ## Aceitando mensagens As mensagens WebSocket são recebidas em ordem, enfileiradas até serem processadas por `ReceiveMessageAsync`. Esse método não retorna mensagem quando o tempo limite é atingido, quando a operação é cancelada ou quando o cliente está desconectado. Só pode haver uma operação de leitura e escrita simultaneamente; portanto, enquanto você aguarda uma mensagem com `ReceiveMessageAsync`, não é possível escrever para o 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("Mensagem recebida: " + msgText); await ws.SendAsync("Olá!"); } return await ws.CloseAsync(); }); ``` ## Conexão persistente O exemplo abaixo mostra como usar uma conexão websocket persistente, onde você recebe as mensagens, as trata e finaliza o uso do socket. ```cs router.MapGet("/connect", async (HttpRequest req) => { using var ws = await req.GetWebSocketAsync(); WebSocketMessage? msg; askName: await ws.SendAsync("Qual é o seu nome?"); msg = await ws.ReceiveMessageAsync(); if (msg is null) return await ws.CloseAsync(); string name = msg.GetString(); if (string.IsNullOrEmpty(name)) { await ws.SendAsync("Por favor, insira seu nome!"); goto askName; } askAge: await ws.SendAsync("E sua idade?"); msg = await ws.ReceiveMessageAsync(); if (msg is null) return await ws.CloseAsync(); if (!Int32.TryParse(msg?.GetString(), out int age)) { await ws.SendAsync("Por favor, insira um número válido"); goto askAge; } await ws.SendAsync($"Você é {name}, e tem {age} anos."); return await ws.CloseAsync(); }); ``` ## Ping Policy Semelhante à política de ping em Server Side Events, você também pode configurar uma política de ping para manter a conexão TCP aberta caso haja inatividade. ```cs ws.PingPolicy.Start( dataMessage: "ping-mensagem", interval: TimeSpan.FromSeconds(10)); ``` ## Conexões gerenciadas Ao aceitar um WebSocket, você pode fornecer um identificador. Sockets identificados são registrados em [HttpServer.WebSockets](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServer.WebSockets.md), permitindo que o servidor encontre conexões ativas fora da rota que as aceitou. ```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("Recebido: " + message.GetString()); } return await ws.CloseAsync(); }); ``` De outra parte da aplicação, consulte a coleção por identificador ou predicado: ```cs HttpWebSocket? socket = server.WebSockets.GetByIdentifier("user:42"); if (socket is { IsClosed: false }) { await socket.SendAsync("Seu relatório está pronto."); } foreach (HttpWebSocket activeSocket in server.WebSockets.Find(id => id.StartsWith("user:"))) { await activeSocket.SendAsync("Mensagem de broadcast"); } ``` Cada `HttpWebSocket` expõe `Identifier`, `State`, `IsClosed` e `PingPolicy`. A coleção também expõe `All()`, `Find(...)`, `GetByIdentifier(...)`, `ActiveConnections` e `DropAll()` para estratégias de conexão gerenciadas pelo servidor. --- # Sintaxe de descarte Source: https://docs.sisk-framework.org/pt-br/docs/features/discard-syntax.html O servidor HTTP pode ser usado para ouvir uma solicitação de callback de uma ação, como autenticação OAuth, e pode ser descartado após receber essa solicitação. Isso pode ser útil em casos em que você precisa de uma ação em segundo plano, mas não deseja configurar um aplicativo HTTP completo para isso. O exemplo a seguir mostra como criar um servidor HTTP de escuta na porta 5555 com [CreateListener](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServer.CreateListener.md) e aguardar o próximo contexto: ```csharp using (var server = HttpServer.CreateListener(5555)) { // aguarde a próxima solicitação HTTP var context = await server.WaitNextAsync(); Console.WriteLine($"Caminho solicitado: {context.Request.Path}"); } ``` A função [WaitNext](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServer.WaitNext.md) aguarda o próximo contexto de processamento de solicitação concluída. Uma vez que o resultado dessa operação é obtido, o servidor já lidou completamente com a solicitação e enviou a resposta para o cliente. --- # Injeção de Dependência Source: https://docs.sisk-framework.org/pt-br/docs/features/instancing.html É comum dedicar membros e instâncias que duram por toda a vida de uma solicitação, como uma conexão de banco de dados, um usuário autenticado ou um token de sessão. Uma das possibilidades é através do [HttpContext.RequestBag](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpContext.md), que cria um dicionário que dura por toda a vida de uma solicitação. Este dicionário pode ser acessado por [tratadores de solicitação](https://docs.sisk-framework.org/pt-br/docs/fundamentals/request-handlers.md) e definir variáveis ao longo da solicitação. Por exemplo, um tratador de solicitação que autentica um usuário define este usuário dentro do `HttpContext.RequestBag`, e dentro da lógica da solicitação, este usuário pode ser recuperado com `HttpContext.RequestBag.Get()`. Os objetos definidos neste dicionário são limitados ao ciclo de vida da solicitação. Eles são descartados no final da solicitação. Não necessariamente, o envio de uma resposta define o fim do ciclo de vida da solicitação. Quando [tratadores de solicitação](https://docs.sisk-framework.org/pt-br/docs/fundamentals/request-handlers.md) que são executados após o envio de uma resposta são executados, os objetos `RequestBag` ainda existem e não foram descartados. Aqui está um exemplo: ```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; // avançar para o próximo tratador de solicitação ou lógica de solicitação } } ``` ```csharp {title="Controllers/HelloController.cs"} [RouteGet("/hello")] [RequestHandler] public HttpResponse SayHello(HttpRequest request) { var authenticatedUser = request.Bag.Get(); return new HttpResponse() { Content = new StringContent($"Hello {authenticatedUser.Name}!") }; } ``` Este é um exemplo preliminar desta operação. A instância de `User` foi criada dentro do tratador de solicitação dedicado à autenticação, e todas as rotas que usam este tratador de solicitação terão a garantia de que haverá um `User` em sua instância de `HttpContext.RequestBag`. É possível definir lógica para obter instâncias quando não previamente definidas no `RequestBag` por meio de métodos como [GetOrAdd](https://docs.sisk-framework.org/api/Sisk.Core.Entity.TypedValueDictionary.GetOrAdd.md) ou [GetOrAddAsync](https://docs.sisk-framework.org/api/Sisk.Core.Entity.TypedValueDictionary.GetOrAddAsync.md). Desde a versão 1.3, a propriedade estática [HttpContext.Current](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpContext.Current.md) foi introduzida, permitindo o acesso ao `HttpContext` atualmente em execução do contexto da solicitação. Isso permite expor membros do `HttpContext` fora da solicitação atual e definir instâncias em objetos de rota. O exemplo abaixo define um controlador que tem membros comumente acessados pelo contexto de uma solicitação. ```csharp {title="Controllers/Controller.cs"} public abstract class Controller : RouterModule { // Obter a instância existente ou criar uma nova instância de banco de dados para esta solicitação protected DbContext Database => HttpContext.Current.RequestBag.GetOrAdd(() => new DbContext()); // Carregar repositórios de forma preguiçosa também é comum 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)); // a seguinte linha lançará uma exceção se a propriedade for acessada quando o User não // estiver definido no request bag protected User AuthenticatedUser => => HttpContext.Current.RequestBag.Get(); // Expor a instância de HttpRequest também é suportado protected HttpRequest Request => HttpContext.Current.Request } ``` E definir tipos que herdam do 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 o exemplo acima, você precisará configurar um [tratador de valor](https://docs.sisk-framework.org/pt-br/docs/fundamentals/responses.md#implicit-response-types) em seu roteador para que os objetos retornados pelo roteador sejam transformados em uma resposta [HttpResponse](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpResponse.md) válida. Observe que os métodos não têm um argumento `HttpRequest request` presente em outros métodos. Isso ocorre porque, desde a versão 1.3, o roteador suporta dois tipos de delegados para respostas de rota: [RouteAction](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouteAction.md), que é o delegado padrão que recebe um argumento `HttpRequest`, e [ParameterlessRouteAction](https://docs.sisk-framework.org/api/Sisk.Core.Routing.ParameterlessRouteAction.md). O objeto `HttpRequest` ainda pode ser acessado por ambos os delegados através da propriedade [Request](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpContext.Request.md) do `HttpContext` estático na thread. No exemplo acima, definimos um objeto descartável, o `DbContext`, e precisamos garantir que todas as instâncias criadas em um `DbContext` sejam descartadas quando a sessão HTTP for finalizada. Para isso, podemos usar duas maneiras de alcançar isso. Uma é criar um [tratador de solicitação](https://docs.sisk-framework.org/pt-br/docs/fundamentals/request-handlers.md) que seja executado após a ação do roteador, e a outra maneira é através de um [tratador de servidor personalizado](https://docs.sisk-framework.org/pt-br/docs/advanced/http-server-handlers.md). Para o primeiro método, podemos criar o tratador de solicitação inline diretamente no método [OnSetup](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouterModule.OnSetup.md) herdado 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) => { // obter uma instância de DbContext definida no contexto do tratador de solicitação e // descartá-la ctx.RequestBag.GetOrDefault()?.Dispose(); return null; }, executionMode: RequestHandlerExecutionMode.AfterResponse)); } } ``` > [!TIP] > > Desde a versão 1.4 do Sisk, a propriedade [HttpServerConfiguration.DisposeDisposableContextValues](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.DisposeDisposableContextValues.md) foi introduzida e habilitada por padrão, que define se o servidor HTTP deve descartar todos os valores `IDisposable` no saco de contexto quando uma sessão HTTP for fechada. O método acima garantirá que o `DbContext` seja descartado quando a sessão HTTP for finalizada. Você pode fazer isso para mais membros que precisam ser descartados no final de uma resposta. Para o segundo método, você pode criar um [tratador de servidor personalizado](https://docs.sisk-framework.org/pt-br/docs/advanced/http-server-handlers.md) que descartará o `DbContext` quando a sessão HTTP for finalizada. ```csharp {title="Server/Handlers/ObjectDisposerHandler.cs"} public class ObjectDisposerHandler : HttpServerHandler { protected override void OnHttpRequestClose(HttpServerExecutionResult result) { result.Context.RequestBag.GetOrDefault()?.Dispose(); } } ``` E usá-lo em seu construtor de aplicativo: ```csharp {title="Program.cs"} using var host = HttpServer.CreateBuilder() .UseHandler() .Build(); ``` Essa é uma maneira de lidar com a limpeza de código e manter as dependências de uma solicitação separadas pelo tipo de módulo que será usado, reduzindo a quantidade de código duplicado dentro de cada ação de um roteador. É uma prática semelhante ao que a injeção de dependência é usada para em frameworks como o ASP.NET. --- # Streaming de Conteúdo Source: https://docs.sisk-framework.org/pt-br/docs/features/content-streaming.html O Sisk suporta a leitura e o envio de fluxos de conteúdo para e do cliente. Essa funcionalidade é útil para remover a sobrecarga de memória para serializar e deserializar conteúdo durante a vida útil de uma solicitação. ## Fluxo de conteúdo da solicitação Pequenos conteúdos são carregados automaticamente no buffer de memória da conexão HTTP, carregando rapidamente esse conteúdo para [HttpRequest.Body](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Body.md) e [HttpRequest.RawBody](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.RawBody.md). Para conteúdos maiores, o método [HttpRequest.GetRequestStream](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.GetRequestStream.md) pode ser usado para obter o fluxo de leitura do conteúdo da solicitação. Vale notar que o método [HttpRequest.GetMultipartFormContent](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.GetMultipartFormContent.md) lê todo o conteúdo da solicitação na memória, portanto, pode não ser útil para ler conteúdos grandes. Considere o seguinte exemplo: ```csharp {title="Controller/UploadDocument.cs"} [RoutePost ( "/api/upload-document/" )] public async Task UploadDocument ( HttpRequest request ) { var fileName = request.RouteParameters [ "filename" ].GetString (); if (!request.HasContents) { // solicitação não tem conteúdo 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 = "Arquivo enviado com sucesso." } ) }; } ``` No exemplo acima, o método `UploadDocument` lê o conteúdo da solicitação e salva o conteúdo em um arquivo. Nenhuma alocação adicional de memória é feita, exceto pelo buffer de leitura usado por `Stream.CopyToAsync`. O exemplo acima remove a pressão de alocação de memória para um arquivo muito grande, o que pode otimizar o desempenho da aplicação. Uma boa prática é sempre usar um [CancellationToken](https://learn.microsoft.com/pt-br/dotnet/api/system.threading.cancellationtoken) em uma operação que possa ser demorada, como enviar arquivos, pois depende da velocidade da rede entre o cliente e o servidor. O ajuste com um CancellationToken pode ser feito da seguinte forma: ```csharp {title="Controller/UploadDocument.cs"} // o token de cancelamento abaixo irá lançar uma exceção se o tempo limite de 30 segundos for atingido. 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 = "O upload excedeu o tempo máximo de upload (30 segundos)." } ) }; } ``` ## Fluxo de conteúdo da resposta Enviar conteúdo de resposta também é possível. Atualmente, existem duas maneiras de fazer isso: através do método [HttpRequest.GetResponseStream](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.GetResponseStream.md) e usando um conteúdo do tipo [StreamContent](https://learn.microsoft.com/pt-br/dotnet/api/system.net.http.streamcontent?view=net-9.0). Considere um cenário em que precisamos servir um arquivo de imagem. Para fazer isso, podemos usar o seguinte código: ```csharp {title="Controller/ImageController.cs"} [RouteGet ( "/api/profile-picture" )] public async Task UploadDocument ( HttpRequest request ) { // método de exemplo para obter uma imagem 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}" } }; } ``` O método acima faz uma alocação de memória a cada vez que lê o conteúdo da imagem. Se a imagem for grande, isso pode causar um problema de desempenho e, em situações de pico, até mesmo uma sobrecarga de memória e travar o servidor. Nesses casos, o cache pode ser útil, mas não eliminará o problema, pois a memória ainda será reservada para esse arquivo. O cache aliviará a pressão de ter que alocar memória para cada solicitação, mas para arquivos grandes, não será suficiente. Enviar a imagem por meio de um fluxo pode ser uma solução para o problema. Em vez de ler todo o conteúdo da imagem, um fluxo de leitura é criado no arquivo e copiado para o cliente usando um buffer pequeno. #### Enviando através do método GetResponseStream O método [HttpRequest.GetResponseStream](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.GetResponseStream.md) cria um objeto que permite enviar pedaços da resposta HTTP à medida que o fluxo de conteúdo é preparado. Esse método é mais manual, exigindo que você defina o status, cabeçalhos e tamanho do conteúdo antes de enviar o conteúdo. ```csharp {title="Controller/ImageController.cs"} [RouteGet ( "/api/profile-picture" )] public async Task UploadDocument ( HttpRequest request ) { var profilePictureFilename = "profile-picture.jpg"; // nessa forma de envio, o status e o cabeçalho devem ser definidos // antes de enviar o conteúdo 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 )) { // nessa forma de envio, também é necessário definir o tamanho do conteúdo // antes de enviá-lo. requestStreamManager.SetContentLength ( fs.Length ); // se você não souber o tamanho do conteúdo, pode usar o chunked-encoding // para enviar o conteúdo requestStreamManager.SendChunked = true; // e então, escrever no fluxo de saída await fs.CopyToAsync ( requestStreamManager.ResponseStream ); } } ``` #### Enviando conteúdo através de um StreamContent A classe [StreamContent](https://learn.microsoft.com/pt-br/dotnet/api/system.net.http.streamcontent?view=net-9.0) permite enviar conteúdo de uma fonte de dados como um fluxo de bytes. Essa forma de envio é mais fácil, removendo os requisitos anteriores e até mesmo permitindo o uso de [codificação de compressão](https://docs.sisk-framework.org/pt-br/docs/fundamentals/responses.md#gzip-deflate-and-brotli-compression) para reduzir o tamanho do conteúdo. ```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] > > Nesse tipo de conteúdo, não encapsule o fluxo em um bloco `using`. O conteúdo será automaticamente descartado pelo servidor HTTP quando o fluxo de conteúdo for finalizado, com ou sem erros. --- # Habilitando CORS (Compartilhamento de Recursos de Origem Cruzada) no Sisk Source: https://docs.sisk-framework.org/pt-br/docs/features/cors.html Sisk tem uma ferramenta que pode ser útil para lidar com [Compartilhamento de Recursos de Origem Cruzada (CORS)](https://developer.mozilla.org/pt-BR/docs/pt-br/Web/HTTP/Guides/CORS) ao expor seu serviço publicamente. Essa funcionalidade não faz parte do protocolo HTTP, mas é uma característica específica dos navegadores da web definida pela W3C. Esse mecanismo de segurança impede que uma página da web faça solicitações para um domínio diferente daquele que forneceu a página da web. Um provedor de serviços pode permitir que certos domínios acessem seus recursos ou apenas um. ## Mesma Origem Para que um recurso seja identificado como "mesma origem", uma solicitação deve identificar o cabeçalho [Origem](https://developer.mozilla.org/pt-BR/docs/pt-br/Web/HTTP/Reference/Headers/Origin) em sua solicitação: ```http GET /api/usuarios HTTP/1.1 Host: example.com Origem: http://example.com ... ``` E o servidor remoto deve responder com um cabeçalho [Access-Control-Allow-Origin](https://developer.mozilla.org/pt-BR/docs/pt-br/Web/HTTP/Headers/Access-Control-Allow-Origin) com o mesmo valor que a origem solicitada: ```http HTTP/1.1 200 OK Access-Control-Allow-Origin: http://example.com ... ``` Essa verificação é **explícita**: o host, porta e protocolo devem ser os mesmos solicitados. Verifique o exemplo: - Um servidor responde que seu `Access-Control-Allow-Origin` é `https://example.com`: - `https://example.net` - o domínio é diferente. - `http://example.com` - o esquema é diferente. - `http://example.com:5555` - a porta é diferente. - `https://www.example.com` - o host é diferente. Na especificação, apenas a sintaxe é permitida para ambos os cabeçalhos, seja para solicitações e respostas. O caminho da URL é ignorado. A porta também é omitida se for uma porta padrão (80 para HTTP e 443 para HTTPS). ```http Origem: null Origem: :// Origem: ://: ``` ## Habilitando CORS Nativamente, você tem o objeto [CrossOriginResourceSharingHeaders](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.md) dentro do seu [ListeningHost](https://docs.sisk-framework.org/api/Sisk.Core.Http.ListeningHost.md). Você pode configurar o CORS ao inicializar o 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(); } ``` O código acima enviará os seguintes cabeçalhos para **todas as respostas**: ```http HTTP/1.1 200 OK Access-Control-Allow-Origin: http://example.com Access-Control-Allow-Headers: Authorization Access-Control-Expose-Headers: Content-Type ``` Esses cabeçalhos precisam ser enviados para todas as respostas para um cliente da web, incluindo erros e redirecionamentos. Você pode notar que a classe [CrossOriginResourceSharingHeaders](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.md) tem duas propriedades semelhantes: [AllowOrigin](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AllowOrigin.md) e [AllowOrigins](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AllowOrigins.md). Observe que uma é plural, enquanto a outra é singular. - A propriedade **AllowOrigin** é estática: apenas a origem que você especificar será enviada para todas as respostas. - A propriedade **AllowOrigins** é dinâmica: o servidor verifica se a origem da solicitação está contida nessa lista. Se for encontrada, ela é enviada para a resposta daquela origem. ### Caracteres Coringa e Cabeçalhos Automáticos Alternativamente, você pode usar um caractere coringa (`*`) na origem da resposta para especificar que qualquer origem é permitida acessar o recurso. No entanto, esse valor não é permitido para solicitações que têm credenciais (cabeçalhos de autorização) e essa operação [resultará em um erro](https://developer.mozilla.org/pt-BR/docs/pt-br/Web/HTTP/Guides/CORS/Errors/CORSNotSupportingCredentials). Você pode contornar esse problema listando explicitamente quais origens serão permitidas por meio da propriedade [AllowOrigins](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AllowOrigins.md) ou também usar a constante [AutoAllowOrigin](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AutoAllowOrigin.md) no valor de [AllowOrigin](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AllowOrigin.md). Essa propriedade mágica definirá o cabeçalho `Access-Control-Allow-Origin` para o mesmo valor que o cabeçalho `Origin` da solicitação. Você também pode usar [AutoFromRequestMethod](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AutoFromRequestMethod.md) e [AutoFromRequestHeaders](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AutoFromRequestHeaders.md) para um comportamento semelhante ao `AllowOrigin`, que responde automaticamente com base nos cabeçalhos enviados. ```csharp using var host = HttpServer.CreateBuilder() .UseCors(new CrossOriginResourceSharingHeaders( // Responde com base no cabeçalho Origin da solicitação allowOrigin: CrossOriginResourceSharingHeaders.AutoAllowOrigin, // Responde com base no cabeçalho Access-Control-Request-Method ou no método da solicitação allowMethods: [CrossOriginResourceSharingHeaders.AutoFromRequestMethod], // Responde com base no cabeçalho Access-Control-Request-Headers ou nos cabeçalhos enviados allowHeaders: [CrossOriginResourceSharingHeaders.AutoFromRequestHeaders], exposeHeaders: [HttpKnownHeaderNames.ContentType, "X-Authenticated-Account-Id"], allowCredentials: true)) .Build(); ``` ## Outras Maneiras de Aplicar CORS Se você estiver lidando com [provedores de serviços](https://docs.sisk-framework.org/pt-br/docs/extensions/service-providers.md), você pode substituir valores definidos no arquivo de configuração: ```csharp static async Task Main(string[] args) { using var app = HttpServer.CreateBuilder() .UsePortableConfiguration(...) .UseCors(cors => { // Substituirá a origem definida no arquivo de configuração. cors.AllowOrigin = "http://example.com"; }) .Build(); await app.StartAsync(); } ``` ## Desabilitando CORS em Rotas Específicas A propriedade `UseCors` está disponível para rotas e todos os atributos de rota e pode ser desabilitada com o seguinte exemplo: ```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" }; } } ``` ## Substituindo Valores na Resposta Você pode substituir ou remover valores explicitamente em uma ação de roteamento: ```csharp [RoutePrefix("api/widgets")] public class WidgetController : Controller { public IEnumerable GetWidgets(HttpRequest request) { // Remove o cabeçalho Access-Control-Allow-Credentials request.Context.OverrideHeaders.AccessControlAllowCredentials = string.Empty; // Substitui o Access-Control-Allow-Origin request.Context.OverrideHeaders.AccessControlAllowOrigin = "https://contorso.com"; return new[] { "Green widget", "Red widget" }; } } ``` ## Solicitações de Pré-voo Uma solicitação de pré-voo é um método [OPTIONS](https://developer.mozilla.org/pt-BR/docs/pt-br/Web/HTTP/Reference/Methods/OPTIONS) que o cliente envia antes da solicitação real. O servidor Sisk sempre responderá à solicitação com um `200 OK` e os cabeçalhos CORS aplicáveis, e então o cliente pode prosseguir com a solicitação real. Essa condição só não se aplica quando uma rota existe para a solicitação com o [RouteMethod](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouteMethod.md) explicitamente configurado para `Options`. ## Desabilitando CORS Globalmente Isso não é possível. Para não usar CORS, não configure-o. --- # Servidor de Arquivos Source: https://docs.sisk-framework.org/pt-br/docs/features/file-server.html Sisk fornece o namespace `Sisk.Http.FileSystem`, que contém ferramentas para servir arquivos estáticos, listagem de diretórios e conversão de arquivos. Esse recurso permite servir arquivos de um diretório local, com suporte a solicitações de intervalo (streaming de áudio/vídeo) e processamento personalizado de arquivos. ## Servindo arquivos estáticos A maneira mais fácil de servir arquivos estáticos é [Router.MapFileSystem](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.MapFileSystem.md). Esse método mapeia um prefixo de URL para um diretório no disco. ```cs using Sisk.Core.Http; using Sisk.Core.Http.FileSystem; // maps the root of the server to the current directory mainRouter.MapFileSystem("/", Directory.GetCurrentDirectory()); // maps /assets to the "public/assets" folder mainRouter.MapFileSystem( "/assets", Path.Combine(Directory.GetCurrentDirectory(), "public", "assets")); ``` Quando uma requisição corresponde ao prefixo da rota, o `HttpFileServerHandler` procurará um arquivo no diretório especificado. Se encontrado, ele servirá o arquivo; caso contrário, retornará uma resposta 404 (ou 403 se o acesso for negado). `HttpFileServer.CreateServingRoute` ainda está disponível quando você precisa criar um objeto `Route` explicitamente, mas `MapFileSystem` é a opção mais direta para o código da aplicação. ## HttpFileServerHandler Para ter mais controle sobre como os arquivos são servidos, você pode instanciar e configurar o `HttpFileServerHandler` manualmente. ```cs var fileHandler = new HttpFileServerHandler("/var/www/html"); // enable directory listing (disabled by default) fileHandler.AllowDirectoryListing = true; // set a custom route prefix (this will be trimmed from the request path) fileHandler.RoutePrefix = "/public"; // register the handler under /public mainRouter.MapFileSystem("/public", fileHandler); ``` ### Configuração | Propriedade | Descrição | |---|---| | `RootDirectoryPath` | O caminho absoluto ou relativo para o diretório raiz a partir do qual os arquivos são servidos. | | `RoutePrefix` | O prefixo da rota que será removido do caminho da requisição ao resolver arquivos. O padrão é `/`. | | `AllowDirectoryListing` | Se definido como `true`, habilita a listagem de diretórios quando um diretório é solicitado e nenhum arquivo índice é encontrado. O padrão é `false`. | | `FileConverters` | Uma lista de `HttpFileServerFileConverter` usada para transformar arquivos antes de servi-los. | ## Listagem de Diretório Quando `AllowDirectoryListing` está habilitado e o usuário solicita um caminho de diretório, o Sisk gerará uma página HTML listando o conteúdo desse diretório. A listagem de diretório inclui: - Navegação para o diretório pai (`..`). - Lista de subdiretórios. - Lista de arquivos com tamanho e data da última modificação. ## Conversores de Arquivo Conversores de arquivo permitem interceptar tipos específicos de arquivos e tratá-los de forma diferente. Por exemplo, você pode querer transcodificar uma imagem, comprimir um arquivo em tempo real ou servir um arquivo usando conteúdo parcial (solicitações de intervalo). O Sisk inclui dois conversores embutidos para streaming de mídia: - `HttpFileAudioConverter`: Lida com `.mp3`, `.ogg`, `.wav`, `.flac`, `.ogv`. - `HttpFileVideoConverter`: Lida com `.webm`, `.avi`, `.mkv`, `.mpg`, `.mpeg`, `.wmv`, `.mov`, `.mp4`. Esses conversores habilitam o suporte a **solicitações de intervalo HTTP**, permitindo que os clientes avancem em arquivos de áudio e vídeo. ### Criando um conversor personalizado Para criar um conversor de arquivo personalizado, herde de `HttpFileServerFileConverter` e implemente `CanConvert` e `Convert`. ```cs using Sisk.Core.Http; using Sisk.Core.Http.FileSystem; public class MyTextConverter : HttpFileServerFileConverter { public override bool CanConvert(FileInfo file) { // apply only to .txt files return file.Extension.Equals(".txt", StringComparison.OrdinalIgnoreCase); } public override HttpResponse Convert(FileInfo file, HttpRequest request) { string content = File.ReadAllText(file.FullName); // uppercase all text content return new HttpResponse(200) { Content = new StringContent(content.ToUpper()) }; } } ``` Então, adicione-o ao seu manipulador: ```cs var handler = new HttpFileServerHandler("./files"); handler.FileConverters.Add(new MyTextConverter()); ``` --- # Protocolo de Contexto de Modelo Source: https://docs.sisk-framework.org/pt-br/docs/extensions/mcp.html É possível criar aplicações que fornecem contexto a modelos de agente usando grandes modelos de linguagem (LLMs) com o pacote [Sisk.ModelContextProtocol](https://www.nuget.org/packages/Sisk.ModelContextProtocol/): ```bash dotnet add package Sisk.ModelContextProtocol ``` Este pacote expõe classes e métodos úteis para construir servidores MCP que operam sobre [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#streamable-http). A implementação atual oferece ferramentas na versão de protocolo `2025-06-18`. > [!NOTE] > > Antes de começar, observe que este pacote está em desenvolvimento e pode apresentar comportamentos que não estão em conformidade com a especificação. Leia os [detalhes do pacote](https://github.com/sisk-http/core/tree/main/extensions/Sisk.ModelContextProtocol) para saber o que está em desenvolvimento e o que ainda não funciona. ## Começando com MCP A classe [McpProvider](https://docs.sisk-framework.org/api/Sisk.ModelContextProtocol.McpProvider.md) é o ponto de entrada para definir um servidor MCP. É um objeto provedor selado que pode ser configurado na inicialização. Sua aplicação Sisk pode ter um ou mais provedores 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}")); })); ``` Se sua aplicação fornecer apenas um provedor MCP, você pode usar o singleton do builder: ```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(); } ``` O endpoint deve aceitar requisições `GET` e `POST`, portanto `MapAny` é o mapeamento de rota mais simples. `HandleMcpRequestAsync` devolve um [HttpResponse](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpResponse.md), e sua rota deve retorná‑lo. Se precisar de múltiplos provedores em um único app, ignore o singleton e chame [McpProvider.HandleRequestAsync](https://docs.sisk-framework.org/api/Sisk.ModelContextProtocol.McpProvider.HandleRequestAsync.md) diretamente em cada rota: ```csharp var mathProvider = new McpProvider("math-server", "Mathematics server", new Version(1, 0)); router.MapAny("/mcp/math", async request => { return await mathProvider.HandleRequestAsync(request); }); ``` ## Criando Schemas JSON para Funções A biblioteca [Sisk.ModelContextProtocol] usa um fork do [LightJson](https://github.com/CypherPotato/LightJson) para manipulação de JSON e schemas JSON. Esta implementação fornece um construtor fluente de Schemas JSON para diversos objetos: - JsonSchema.CreateObjectSchema - JsonSchema.CreateArraySchema - JsonSchema.CreateBooleanSchema - JsonSchema.CreateNumberSchema - JsonSchema.CreateStringSchema - JsonSchema.Empty Exemplo: ```csharp JsonSchema.CreateObjectSchema( properties: new Dictionary() { { "numbers", JsonSchema.CreateArraySchema( itemsSchema: JsonSchema.CreateNumberSchema(), minItems: 1, description: "The numbers to sum.") } }, requiredProperties: ["numbers"]); ``` Produz o seguinte schema: ```json { "type": "object", "properties": { "numbers": { "type": "array", "items": { "type": "number" }, "minItems": 1, "description": "The numbers to sum." } }, "required": ["numbers"] } ``` ## Manipulando Chamadas de Função A função definida no parâmetro `executionHandler` de [McpTool](https://docs.sisk-framework.org/api/Sisk.ModelContextProtocol.McpTool.md) fornece um JsonObject contendo os argumentos da chamada, que podem ser lidos de forma fluente: ```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) => { // ler o nome da ação. lançará exceção se for nulo ou não for uma string explícita string actionName = context.Arguments["action_name"].GetString(); // action_data é definido como não obrigatório, portanto pode ser nulo aqui string? actionData = context.Arguments["action_data"].MaybeNull()?.GetString(); // Manipular a ação do navegador com base no actionName return await Task.FromResult( McpToolResult.CreateText($"Performed browser action: {actionName}")); })); ``` Os argumentos da ferramenta são validados contra o schema antes que seu manipulador seja executado. Se a validação falhar, o provedor devolve um resultado de erro ao cliente MCP e não invoca o manipulador da ferramenta. ## Resultados de Função O objeto [McpToolResult](https://docs.sisk-framework.org/api/Sisk.ModelContextProtocol.McpToolResult.md) oferece três métodos para criar conteúdo para a resposta de uma ferramenta: - [CreateAudio(ReadOnlySpan, string)](https://docs.sisk-framework.org/api/Sisk.ModelContextProtocol.McpToolResult.CreateAudio.md): cria uma resposta baseada em áudio para o cliente MCP. - [CreateImage(ReadOnlySpan, string)](https://docs.sisk-framework.org/api/Sisk.ModelContextProtocol.McpToolResult.CreateImage.md): cria uma resposta baseada em imagem para o cliente MCP. - [CreateText(string)](https://docs.sisk-framework.org/api/Sisk.ModelContextProtocol.McpToolResult.CreateText.md): cria uma resposta baseada em texto (padrão) para o cliente MCP. Além disso, é possível combinar múltiplos conteúdos diferentes em uma única resposta JSON de ferramenta: ```csharp mcp.Tools.Add(new McpTool( ... executionHandler: async (McpToolContext context) => { // simular trabalho real byte[] browserScreenshot = await browser.ScreenshotAsync(); return McpToolResult.Combine( McpToolResult.CreateText("Heres the screenshot of the browser:"), McpToolResult.CreateImage(browserScreenshot, "image/png") ); })); ``` O provedor atualmente lida com inicialização, `tools/list`, `tools/call`, `ping` e `notifications/*`. Métodos JSON‑RPC não suportados retornam uma resposta de erro JSON‑RPC. ## Trabalho Contínuo O Protocolo de Contexto de Modelo é um protocolo de comunicação para modelos de agente e aplicações que fornecem conteúdo a eles. É um protocolo novo, portanto é comum que sua especificação seja constantemente atualizada com descontinuações, novos recursos e mudanças incompatíveis. É crucial entender os problemas que o [Model Context Protocol](https://modelcontextprotocol.io/docs/pt-br/getting-started/intro) resolve antes de começar a construir aplicações de agente. Também leia a especificação do pacote [Sisk.ModelContextProtocol](https://github.com/sisk-http/core/tree/main/extensions/Sisk.ModelContextProtocol) para compreender seu progresso, status e o que pode ser feito com ele. --- # Extensão JSON-RPC Source: https://docs.sisk-framework.org/pt-br/docs/extensions/json-rpc.html Sisk possui um módulo experimental para uma API [JSON-RPC 2.0](https://www.jsonrpc.org/specification), que permite criar aplicações ainda mais simples. Esta extensão implementa estritamente a interface de transporte JSON-RPC 2.0 e oferece transporte via requisições HTTP GET, POST, e também web-sockets com Sisk. Você pode instalar a extensão via Nuget com o comando abaixo. Observe que, em versões experimentais/beta, você deve habilitar a opção de buscar pacotes pré‑lançamento no Visual Studio. ```bash dotnet add package Sisk.JsonRpc ``` ## Interface de Transporte JSON-RPC é um protocolo de chamada de procedimento remoto (RPC) assíncrono e sem estado que usa JSON para comunicação de dados. Uma requisição JSON-RPC é tipicamente identificada por um ID, e uma resposta é entregue com o mesmo ID que foi enviado na requisição. Nem todas as requisições exigem uma resposta, sendo chamadas de "notificações". A [especificação JSON-RPC 2.0](https://www.jsonrpc.org/specification) explica em detalhes como o transporte funciona. Esse transporte é agnóstico quanto ao local onde será usado. Sisk implementa esse protocolo via HTTP, seguindo as conformidades de [JSON-RPC over HTTP](https://www.jsonrpc.org/historical/json-rpc-over-http.html), que suporta parcialmente requisições GET, mas suporta completamente requisições POST. Web-sockets também são suportados, fornecendo comunicação assíncrona de mensagens. Uma requisição JSON-RPC se parece com: ```json { "jsonrpc": "2.0", "method": "Sum", "params": [1, 2, 4], "id": 1 } ``` E uma resposta bem‑sucedida se parece com: ```json { "jsonrpc": "2.0", "result": 7, "id": 1 } ``` ## Métodos JSON-RPC O exemplo a seguir mostra como criar uma API JSON-RPC usando Sisk. Uma classe de operações matemáticas executa as operações remotas e entrega a resposta serializada ao cliente. ```csharp {title="Program.cs"} using var app = HttpServer.CreateBuilder(port: 5555) .UseJsonRPC((sender, args) => { // adiciona todos os métodos marcados com WebMethod ao manipulador JSON-RPC args.Handler.Methods.AddMethodsFromType(new MathOperations()); // mapeia a rota /service para lidar com requisições JSON-RPC POST e GET args.Router.MapPost("/service", args.Handler.Transport.HttpPost); args.Router.MapGet("/service", args.Handler.Transport.HttpGet); // mapeia o transporte JSON-RPC WebSocket em 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); } } ``` O exemplo acima mapeará os métodos `Sum` e `Sqrt` para o manipulador JSON-RPC, e esses métodos estarão disponíveis em `GET /service`, `POST /service` e `GET /ws`. Os nomes dos métodos não diferenciam maiúsculas de minúsculas. Os parâmetros dos métodos são automaticamente desserializados para seus tipos específicos. O uso de uma requisição com parâmetros nomeados também é suportado. A serialização JSON é feita pela biblioteca [LightJson](https://github.com/CypherPotato/LightJson). Quando um tipo não é desserializado corretamente, você pode criar um [conversor JSON](https://github.com/CypherPotato/LightJson?tab=readme-ov-file#json-converters) específico para esse tipo e associá‑lo a [JsonRpcHandler.JsonSerializerOptions](https://docs.sisk-framework.org/api/Sisk.JsonRPC.JsonRpcHandler.JsonSerializerOptions.md). Você também pode obter o objeto bruto `$.params` da requisição JSON-RPC diretamente no seu método. ```csharp {title="MathOperations.cs"} [WebMethod] public float Sum(JsonArray|JsonObject @params) { ... } ``` Para que isso ocorra, `@params` deve ser o **único** parâmetro do seu método, com exatamente o nome `params` (em C#, o `@` é necessário para escapar esse nome de parâmetro). A desserialização de parâmetros ocorre tanto para objetos nomeados quanto para arrays posicionais. Por exemplo, o método a seguir pode ser chamado remotamente por ambas as requisições: ```csharp [WebMethod] public float AddUserToStore(string apiKey, User user, UserStore store) { ... } ``` Para um array, a ordem dos parâmetros deve ser respeitada. ```json { "jsonrpc": "2.0", "method": "AddUserToStore", "params": [ "1234567890", { "name": "John Doe", "email": "john@example.com" }, { "name": "My Store" } ], "id": 1 } ``` ## Personalizando o serializador Você pode personalizar o serializador JSON na propriedade [JsonRpcHandler.JsonSerializerOptions](https://docs.sisk-framework.org/api/Sisk.JsonRPC.JsonRpcHandler.JsonSerializerOptions.md). Nessa propriedade, você pode habilitar o uso de [JSON5](https://json5.org/) para desserializar mensagens. Embora não seja uma conformidade com JSON-RPC 2.0, JSON5 é uma extensão do JSON que permite uma escrita mais legível e compreensível. ```csharp {title="Program.cs"} using var host = HttpServer.CreateBuilder ( 5556 ) .UseJsonRPC ( ( o, e ) => { // usa um comparador de nomes sanitizado. este comparador compara apenas letras // e dígitos em um nome, e descarta outros símbolos. ex: // foo_bar10 == FooBar10 e.Handler.JsonSerializerOptions.PropertyNameComparer = new JsonSanitizedComparer ( ); // habilita JSON5 para o interpretador JSON. mesmo ativando isso, JSON puro ainda é permitido e.Handler.JsonSerializerOptions.SerializationFlags = LightJson.Serialization.JsonSerializationFlags.Json5; // mapeia a rota POST /service para o manipulador JSON RPC e.Router.MapPost ( "/service", e.Handler.Transport.HttpPost ); } ) .Build ( ); host.Start ( ); ``` --- # Proxy SSL Source: https://docs.sisk-framework.org/pt-br/docs/extensions/ssl-proxy.html > [!WARNING] > Este recurso é experimental e não deve ser usado em produção. Por favor, consulte [este documento](https://docs.sisk-framework.org/pt-br/docs/deploying.md#proxying-your-application) se você deseja fazer o Sisk funcionar com SSL. O Proxy SSL do Sisk é um módulo que fornece uma conexão HTTPS para um [ListeningHost](https://docs.sisk-framework.org/api/Sisk.Core.Http.ListeningHost.md) no Sisk e roteia mensagens HTTPS para um contexto HTTP não seguro. O módulo foi criado para fornecer conexão SSL para um serviço que usa [HttpListener](https://learn.microsoft.com/pt-br/dotnet/api/system.net.httplistener?view=net-8.0) para executar, que não suporta SSL. O proxy é executado dentro da mesma aplicação e ouve mensagens HTTP/1.1, encaminhando-as no mesmo protocolo para o Sisk. Atualmente, este recurso é altamente experimental e pode ser instável o suficiente para não ser usado em produção. No momento, o SslProxy suporta quase todos os recursos do HTTP/1.1, como keep-alive, codificação em chunk, websockets, etc. Para uma conexão aberta ao proxy SSL, uma conexão TCP é criada para o servidor de destino e o proxy é encaminhado para a conexão estabelecida. O SslProxy pode ser usado com HttpServer.CreateBuilder da seguinte forma: ```csharp using var app = HttpServer.CreateBuilder(port: 5555) .UseRouter(r => { r.MapGet("/", request => { return new HttpResponse("Olá, mundo!"); }); }) // adicione SSL ao projeto .UseSsl( sslListeningPort: 5567, new X509Certificate2(@".\ssl.pfx", password: "12345") ) .Build(); app.Start(); ``` Você deve fornecer um certificado SSL válido para o proxy. Para garantir que o certificado seja aceito pelos navegadores, lembre-se de importá-lo para o sistema operacional para que funcione corretamente. --- # Autenticação Básica Source: https://docs.sisk-framework.org/pt-br/docs/extensions/basic-auth.html O pacote de Autenticação Básica adiciona um manipulador de solicitações capaz de lidar com o esquema de autenticação básica em seu aplicativo Sisk com muito pouca configuração e esforço. A autenticação HTTP básica é uma forma minimalista de autenticar solicitações por um ID de usuário e senha, onde a sessão é controlada exclusivamente pelo cliente e não há tokens de autenticação ou acesso. ![Autenticação Básica](https://docs.sisk-framework.org/assets/img/basic-auth.svg) Leia mais sobre o esquema de autenticação básica na [especificação MDN](https://developer.mozilla.org/pt-BR/docs/pt-br/Web/HTTP/Authentication). ## Instalando Para começar, instale o pacote Sisk.BasicAuth em seu projeto: > dotnet add package Sisk.BasicAuth Você pode ver mais maneiras de instalá-lo em seu projeto no [repositório Nuget](https://www.nuget.org/packages/Sisk.BasicAuth/0.15.0). ## Criando seu manipulador de autenticação Você pode controlar o esquema de autenticação para um módulo inteiro ou para rotas individuais. Para isso, vamos primeiro escrever nosso primeiro manipulador de autenticação básica. No exemplo abaixo, uma conexão é feita com o banco de dados, verifica se o usuário existe e se a senha é válida, e após isso, armazena o usuário na bolsa de contexto. ```cs public class UserAuthHandler : BasicAuthenticateRequestHandler { public UserAuthHandler() : base() { Realm = "Para entrar nesta página, por favor, informe suas credenciais."; } public override HttpResponse? OnValidating(BasicAuthenticationCredentials credentials, HttpContext context) { DbContext db = new DbContext(); // nesse caso, estamos usando o e-mail como o campo de ID do usuário, então vamos // procurar por um usuário usando seu e-mail. User? user = db.Users.FirstOrDefault(u => u.Email == credentials.UserId); if (user == null) { return base.CreateUnauthorizedResponse("Desculpe! Nenhum usuário foi encontrado por este e-mail."); } // valida que a senha das credenciais é válida para este usuário. if (!user.ValidatePassword(credentials.Password)) { return base.CreateUnauthorizedResponse("Credenciais inválidas."); } // adiciona o usuário conectado ao contexto HTTP // e continua a execução context.Bag.Add("loggedUser", user); return null; } } ``` Então, basta associar este manipulador de solicitação com nossa rota ou classe. ```cs public class UsersController { [RouteGet("/")] [RequestHandler(typeof(UserAuthHandler))] public string Index(HttpRequest request) { User loggedUser = request.Bag.Get(); return $"Olá, {loggedUser.Name}!"; } } ``` Ou usando a classe [RouterModule](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouterModule.md): ```cs public class UsersController : RouterModule { public ClientModule() { // todas as rotas dentro desta classe serão manipuladas por // UserAuthHandler. base.HasRequestHandler(new UserAuthHandler()); } [RouteGet("/")] public string Index(HttpRequest request) { User loggedUser = request.Bag.Get(); return $"Olá, {loggedUser.Name}!"; } } ``` ## Observações A responsabilidade principal da autenticação básica é realizada no lado do cliente. Armazenamento, controle de cache e criptografia são todos tratados localmente no cliente. O servidor apenas recebe as credenciais e valida se o acesso é permitido ou não. Observe que este método não é um dos mais seguros, pois coloca uma grande responsabilidade no cliente, que pode ser difícil de rastrear e manter a segurança de suas credenciais. Além disso, é crucial que as senhas sejam transmitidas em um contexto de conexão segura (SSL), pois elas não têm criptografia inerente. Uma breve interceptação nos cabeçalhos de uma solicitação pode expor as credenciais de acesso do seu usuário. Opte por soluções de autenticação mais robustas para aplicativos em produção e evite usar muitos componentes prontos, pois eles podem não se adaptar às necessidades do seu projeto e acabar expô-lo a riscos de segurança. --- # Fornecedores de Serviços Source: https://docs.sisk-framework.org/pt-br/docs/extensions/service-providers.html Fornecedores de Serviços é uma forma de portar seu aplicativo Sisk para diferentes ambientes com um arquivo de configuração portátil. Essa funcionalidade permite alterar a porta do servidor, parâmetros e outras opções sem precisar modificar o código do aplicativo para cada ambiente. Esse módulo depende da sintaxe de construção do Sisk e pode ser configurado por meio do método UsePortableConfiguration. Um provedor de configuração é implementado com IConfigurationProvider, que fornece um leitor de configuração e pode receber qualquer implementação. Por padrão, o Sisk fornece um leitor de configuração JSON, mas também há um pacote para arquivos INI. Você também pode criar seu próprio provedor de configuração e registrá-lo com: ```csharp using var app = HttpServer.CreateBuilder() .UsePortableConfiguration(config => { config.WithConfigReader(); }) .Build(); ``` Como mencionado anteriormente, o provedor padrão é um arquivo JSON. Por padrão, o nome do arquivo procurado é service-config.json, e ele é procurado no diretório atual do processo em execução, não no diretório do executável. Você pode escolher alterar o nome do arquivo, bem como onde o Sisk deve procurar o arquivo de configuração, com: ```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(); ``` O código acima procurará o arquivo config.toml no diretório atual do processo em execução. Se não for encontrado, ele procurará no diretório onde o executável está localizado. Se o arquivo não existir, o parâmetro createIfDontExists será honrado, criando o arquivo, sem conteúdo, no último caminho testado (com base em lookupDirectories), e um erro será lançado no console, impedindo que o aplicativo seja inicializado. > [!TIP] > > Você pode olhar o código-fonte do leitor de configuração INI e do leitor de configuração JSON para entender como um IConfigurationProvider é implementado. ## Lendo configurações de um arquivo JSON Por padrão, o Sisk fornece um provedor de configuração que lê configurações de um arquivo JSON. Esse arquivo segue uma estrutura fixa e é composto pelos seguintes parâmetros: ```json { "Server": { "DefaultEncoding": "UTF-8", "ThrowExceptions": true, "IncludeRequestIdHeader": true }, "ListeningHost": { "Label": "Meu aplicativo Sisk", "Ports": [ "http://localhost:80/", "https://localhost:443/", // Arquivos de configuração também suportam comentários ], "CrossOriginResourceSharingPolicy": { "AllowOrigin": "*", "AllowOrigins": [ "*" ], // Novo no 0.14 "AllowMethods": [ "*" ], "AllowHeaders": [ "*" ], "MaxAge": 3600 }, "Parameters": { "MySqlConnection": "server=localhost;user=root;" } } } ``` Os parâmetros criados a partir de um arquivo de configuração podem ser acessados no construtor do servidor: ```csharp using var app = HttpServer.CreateBuilder() .UsePortableConfiguration(config => { config.WithParameters(paramCollection => { string databaseConnection = paramCollection.GetValueOrThrow("MySqlConnection"); }); }) .Build(); ``` Cada leitor de configuração fornece uma forma de ler os parâmetros de inicialização do servidor. Algumas propriedades são indicadas para estar no ambiente do processo em vez de serem definidas no arquivo de configuração, como dados de API sensíveis, chaves de API, etc. ## Estrutura do arquivo de configuração O arquivo de configuração JSON é composto pelas seguintes propriedades:
    Propriedade Obrigatório Descrição
    Server Obrigatório Representa o servidor em si com suas configurações.
    Server.AccessLogsStream Opcional Padrão para console. Especifica o fluxo de saída do log de acesso. Pode ser um nome de arquivo, null ou console.
    Server.ErrorsLogsStream Opcional Padrão para null. Especifica o fluxo de saída do log de erros. Pode ser um nome de arquivo, null ou console.
    Server.MaximumContentLength Opcional
    Server.MaximumContentLength Opcional Padrão para 0. Especifica o comprimento máximo de conteúdo em bytes. Zero significa infinito.
    Server.IncludeRequestIdHeader Opcional Padrão para false. Especifica se o servidor HTTP deve enviar o cabeçalho X-Request-Id.
    Server.ThrowExceptions Opcional Padrão para true. Especifica se as exceções não tratadas devem ser lançadas. Defina como false quando em produção e true quando em depuração.
    ListeningHost Obrigatório Representa o host de escuta do servidor.
    ListeningHost.Label Opcional Representa o rótulo do aplicativo.
    ListeningHost.Ports Obrigatório Representa uma matriz de strings, correspondendo à sintaxe ListeningPort.
    ListeningHost.CrossOriginResourceSharingPolicy Opcional Configura os cabeçalhos CORS para o aplicativo.
    ListeningHost.CrossOriginResourceSharingPolicy.AllowCredentials Opcional Padrão para false. Especifica o cabeçalho Allow-Credentials.
    ListeningHost.CrossOriginResourceSharingPolicy.ExposeHeaders Opcional Padrão para null. Essa propriedade espera uma matriz de strings. Especifica o cabeçalho Expose-Headers.
    ListeningHost.CrossOriginResourceSharingPolicy.AllowOrigin Opcional Padrão para null. Essa propriedade espera uma string. Especifica o cabeçalho Allow-Origin.
    ListeningHost.CrossOriginResourceSharingPolicy.AllowOrigins Opcional Padrão para null. Essa propriedade espera uma matriz de strings. Especifica vários cabeçalhos Allow-Origin. Veja AllowOrigins para mais informações.
    ListeningHost.CrossOriginResourceSharingPolicy.AllowMethods Opcional Padrão para null. Essa propriedade espera uma matriz de strings. Especifica o cabeçalho Allow-Methods.
    ListeningHost.CrossOriginResourceSharingPolicy.AllowHeaders Opcional Padrão para null. Essa propriedade espera uma matriz de strings. Especifica o cabeçalho Allow-Headers.
    ListeningHost.CrossOriginResourceSharingPolicy.MaxAge Opcional Padrão para null. Essa propriedade espera um inteiro. Especifica o cabeçalho Max-Age em segundos.
    ListeningHost.Parameters Opcional Especifica as propriedades fornecidas ao método de configuração do aplicativo.
    --- # Configuração INI Source: https://docs.sisk-framework.org/pt-br/docs/extensions/ini-configuration.html Sisk tem um método para obter configurações de inicialização além do JSON. Na verdade, qualquer pipeline que implemente [IConfigurationReader](https://docs.sisk-framework.org/api/Sisk.Core.Http.Hosting.IConfigurationReader.md) pode ser usado com [PortableConfigurationBuilder.WithConfigurationPipeline](https://docs.sisk-framework.org/api/Sisk.Core.Http.Hosting.PortableConfigurationBuilder.md), lendo a configuração do servidor de qualquer tipo de arquivo. O pacote [Sisk.IniConfiguration](https://www.nuget.org/packages/Sisk.IniConfiguration/) fornece um leitor de arquivos INI baseado em fluxo que não lança exceções para erros de sintaxe comuns e tem uma sintaxe de configuração simples. Esse pacote pode ser usado fora do framework Sisk, oferecendo flexibilidade para projetos que requerem um leitor de documentos INI eficiente. ## Instalando Para instalar o pacote, você pode começar com: ```bash $ dotnet add package Sisk.IniConfiguration ``` Você também pode instalar o pacote principal, que não inclui o [IConfigurationReader](https://docs.sisk-framework.org/api/Sisk.Core.Http.Hosting.IConfigurationReader) INI, nem a dependência do Sisk, apenas os serializadores INI: ```bash $ dotnet add package Sisk.IniConfiguration.Core ``` Com o pacote principal, você pode usá-lo em seu código como mostrado no exemplo abaixo: ```cs class Program { static HttpServerHostContext Host = null!; static void Main(string[] args) { Host = HttpServer.CreateBuilder() .UsePortableConfiguration(config => { config.WithConfigFile("app.ini", createIfDontExists: true); // usa o leitor de configuração 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}!"); } } ``` O código acima procurará por um arquivo app.ini no diretório atual do processo (CurrentDirectory). O arquivo INI tem a seguinte aparência: ```ini [Server] # Múltiplos endereços de escuta são suportados 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 e sintaxe INI Implementação atual do sabor: - Nomes de propriedades e seções são **insensíveis a letras maiúsculas e minúsculas**. - Nomes de propriedades e valores são **recortados**, a menos que os valores sejam citados. - Valores podem ser citados com aspas simples ou duplas. Aspas podem ter quebras de linha dentro delas. - Comentários são suportados com `#` e `;`. Além disso, **comentários de tralha são permitidos**. - Propriedades podem ter múltiplos valores. Em detalhes, a documentação para o "sabor" do analisador INI usado no Sisk está [disponível neste documento](https://github.com/sisk-http/archive/blob/master/ext/ini-reader-syntax.md). Usando o seguinte código INI como exemplo: ```ini One = 1 Value = this is an value Another value = "this value has an line break on it" ; o código abaixo tem algumas cores [some section] Color = Red Color = Blue Color = Yellow ; não use amarelo ``` Analisá-lo com: ```csharp // analisa o texto INI da string IniDocument doc = IniDocument.FromString(iniText); // obtenha um valor string? one = doc.Global.GetOne("one"); string? anotherValue = doc.Global.GetOne("another value"); // obtenha múltiplos valores string[]? colors = doc.GetSection("some section")?.GetMany("color"); ``` ## Parâmetros de configuração | Seção e nome | Permite múltiplos valores | Descrição | | ---------------- | --------------------- | ----------- | | `Server.Listen` | Sim | Os endereços/ports de escuta do servidor. | | `Server.Encoding` | Não | A codificação padrão do servidor. | | `Server.MaximumContentLength` | Não | O tamanho máximo do conteúdo em bytes. | | `Server.IncludeRequestIdHeader` | Não | Especifica se o servidor HTTP deve enviar o cabeçalho X-Request-Id. | | `Server.ThrowExceptions` | Não | Especifica se as exceções não tratadas devem ser lançadas. | | `Server.AccessLogsStream` | Não | Especifica o fluxo de saída de logs de acesso. | | `Server.ErrorsLogsStream` | Não | Especifica o fluxo de saída de logs de erros. | | `Cors.AllowMethods` | Não | Especifica o valor do cabeçalho CORS Allow-Methods. | | `Cors.AllowHeaders` | Não | Especifica o valor do cabeçalho CORS Allow-Headers. | | `Cors.AllowOrigins` | Não | Especifica múltiplos cabeçalhos Allow-Origin, separados por vírgulas. [AllowOrigins](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AllowOrigins.md) para mais informações. | | `Cors.AllowOrigin` | Não | Especifica um cabeçalho Allow-Origin. | | `Cors.ExposeHeaders` | Não | Especifica o valor do cabeçalho CORS Expose-Headers. | | `Cors.AllowCredentials` | Não | Especifica o valor do cabeçalho CORS Allow-Credentials. | | `Cors.MaxAge` | Não | Especifica o valor do cabeçalho CORS Max-Age. --- # Documentação da API Source: https://docs.sisk-framework.org/pt-br/docs/extensions/api-documentation.html A extensão `Sisk.Documenting` permite gerar documentação de API para sua aplicação Sisk automaticamente. Ela aproveita a estrutura do seu código e os atributos para criar um site de documentação abrangente, suportando exportação para o formato Open API (Swagger). > [!WARNING] > Este pacote está atualmente em desenvolvimento e ainda não foi publicado. Seu comportamento e API podem estar sujeitos a alterações em atualizações futuras. Como este pacote ainda não está disponível no NuGet, você deve incorporar o código-fonte diretamente ao seu projeto ou referenciá-lo como uma dependência de projeto. Você pode acessar o código-fonte [aqui](https://github.com/sisk-http/core/tree/main/extensions/Sisk.Documenting). Para usar `Sisk.Documenting`, você precisa registrá-lo no construtor da sua aplicação e decorar seus manipuladores de rotas com atributos de documentação. ### Registrando a geração de documentação Use o método de extensão `UseApiDocumentation` no seu `HttpServerHostContextBuilder` para expor a documentação de API gerada a partir do mesmo roteador que serve sua aplicação. ```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 metadados sobre sua aplicação, como nome, descrição e versão. - **routerPath**: O caminho URL onde a interface de usuário da documentação (ou JSON) estará acessível. - **exporter**: Configura como a documentação é exportada. O `OpenApiExporter` habilita o suporte a Open API (Swagger). ### Documentando Endpoints Você pode descrever seus endpoints usando os atributos `[ApiEndpoint]` e `[ApiQueryParameter]` nos seus métodos manipuladores de rotas. ### `ApiEndpoint` O atributo `[ApiEndpoint]` permite que você forneça uma descrição para o endpoint. ```csharp [ApiEndpoint(Description = "Returns a greeting message.")] public HttpResponse Index(HttpRequest request) { ... } ``` ### `ApiQueryParameter` O atributo `[ApiQueryParameter]` documenta os parâmetros de string de consulta que o endpoint aceita. ```csharp [ApiQueryParameter(name: "name", IsRequired = false, Description = "The name of the person to greet.", Type = "string")] public HttpResponse Index(HttpRequest request) { ... } ``` - **name**: O nome do parâmetro de consulta. - **IsRequired**: Especifica se o parâmetro é obrigatório. - **Description**: Uma descrição legível do parâmetro. - **Type**: O tipo de dado esperado (ex.: "string", "int"). ### `ApiEndpoint` Anota um endpoint com informações gerais. * **Name** (string, required in constructor): O nome do endpoint da API. * **Description** (string): Uma breve descrição do que o endpoint faz. * **Group** (string): Permite agrupar endpoints (ex.: por controlador ou módulo). * **InheritDescriptionFromXmlDocumentation** (bool, default: `true`): Se `true`, tenta usar o resumo da documentação XML do método caso `Description` não esteja definido. ### `ApiHeader` Documenta um cabeçalho HTTP específico que o endpoint espera ou utiliza. * **HeaderName** (string, required in constructor): A chave do cabeçalho (ex.: "Authorization"). * **Description** (string): Descreve o propósito do cabeçalho. * **IsRequired** (bool): Indica se o cabeçalho é obrigatório para a requisição. ### `ApiParameter` Define um parâmetro genérico para o endpoint, frequentemente usado para campos de formulário ou parâmetros de corpo que não são cobertos por outros atributos. * **Name** (string, required in constructor): O nome do parâmetro. * **TypeName** (string, required in constructor): O tipo de dado do parâmetro (ex.: "string", "int"). * **Description** (string): Uma descrição do parâmetro. * **IsRequired** (bool): Indica se o parâmetro é obrigatório. ### `ApiParametersFrom` Gera automaticamente a documentação de parâmetros a partir das propriedades de uma classe ou tipo especificado. * **Type** (Type, required in constructor): O `Type` da classe a partir do qual refletir as propriedades. ### `ApiPathParameter` Documenta uma variável de caminho (ex.: em `/users/{id}`). * **Name** (string, required in constructor): O nome do parâmetro de caminho. * **Description** (string): Descreve o que o parâmetro representa. * **Type** (string): O tipo de dado esperado. ### `ApiQueryParameter` Documenta um parâmetro de string de consulta (ex.: `?page=1`). * **Name** (string, required in constructor): A chave do parâmetro de consulta. * **Description** (string): Descreve o parâmetro. * **Type** (string): O tipo de dado esperado. * **IsRequired** (bool): Indica se o parâmetro de consulta deve estar presente. ### `ApiRequest` Descreve o corpo da requisição esperado. * **Description** (string, required in constructor): Uma descrição do corpo da requisição. * **Example** (string): Uma string bruta contendo um exemplo do corpo da requisição. * **ExampleLanguage** (string): A linguagem do exemplo (ex.: "json", "xml"). * **PayloadType** (Type): Se definido, o exemplo e o esquema serão gerados automaticamente a partir desse tipo quando os manipuladores de contexto configurados o suportarem. ### `ApiResponse` Descreve uma resposta possível do endpoint. * **StatusCode** (HttpStatusCode, required in constructor): O código de status HTTP retornado (ex.: `HttpStatusCode.OK`). * **Description** (string): Descreve a condição para esta resposta. * **Example** (string): Uma string bruta contendo um exemplo do corpo da resposta. * **ExampleLanguage** (string): A linguagem do exemplo. * **PayloadType** (Type): Se definido, o exemplo e o esquema serão gerados automaticamente a partir desse tipo quando os manipuladores de contexto configurados o suportarem. ## Manipuladores de Tipo Os manipuladores de tipo são responsáveis por converter seus tipos .NET (classes, enums, etc.) em exemplos de documentação. Isso é particularmente útil para gerar exemplos automáticos de corpos de requisição e resposta com base em seus modelos de dados. Esses manipuladores são configurados dentro do `ApiGenerationContext`. ```csharp using Sisk.Documenting.Content; var context = new ApiGenerationContext() { // ... BodyExampleTypeHandler = new JsonContentTypeHandler(), ParameterExampleTypeHandler = new JsonContentTypeHandler(), ContentSchemaTypeHandler = new JsonContentTypeHandler() }; ``` ### JsonContentTypeHandler O `JsonContentTypeHandler` é um manipulador embutido que gera exemplos JSON, exemplos de parâmetros e esquemas JSON. Ele implementa `IExampleBodyTypeHandler`, `IExampleParameterTypeHandler` e `IContentSchemaTypeHandler`. Ele pode ser customizado com opções específicas de `JsonSerializerOptions` ou `IJsonTypeInfoResolver` para corresponder à lógica de serialização da sua aplicação. ```csharp var jsonHandler = new JsonContentTypeHandler(new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, WriteIndented = true }); context.BodyExampleTypeHandler = jsonHandler; context.ParameterExampleTypeHandler = jsonHandler; context.ContentSchemaTypeHandler = jsonHandler; ``` ### Custom Type Handlers Você pode implementar seus próprios manipuladores para suportar outros formatos (como XML) ou para personalizar como os exemplos são gerados. #### IExampleBodyTypeHandler Implemente esta interface para gerar exemplos de corpo para tipos de requisição e resposta. ```csharp public class XmlExampleTypeHandler : IExampleBodyTypeHandler { public BodyExampleResult? GetBodyExampleForType(Type type) { // Generate XML string for the type string xmlContent = MyXmlGenerator.Generate(type); return new BodyExampleResult(xmlContent, "xml"); } } ``` #### IExampleParameterTypeHandler Implemente esta interface para gerar descrições detalhadas de parâmetros a partir de um 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 Os exportadores são responsáveis por converter os metadados de documentação de API coletados em um formato específico que pode ser consumido por outras ferramentas ou exibido ao usuário. ### OpenApiExporter O exportador padrão fornecido é o `OpenApiExporter`, que gera um arquivo JSON seguindo a [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" } ``` ### Creating a Custom Exporter Você pode criar seu próprio exportador implementando a interface `IApiDocumentationExporter`. Isso permite que você exporte a documentação em formatos como Markdown, HTML, Postman Collection ou qualquer outro formato customizado. A interface requer que você implemente um único método: `ExportDocumentationContent`. ```csharp using Sisk.Core.Http; using Sisk.Documenting; public class MyCustomExporter : IApiDocumentationExporter { public HttpContent ExportDocumentationContent(ApiDocumentation documentation) { // 1. Process the documentation object var sb = new StringBuilder(); sb.AppendLine($"# {documentation.ApplicationName}"); foreach(var endpoint in documentation.Endpoints) { sb.AppendLine($"## {endpoint.Method} {endpoint.Path}"); sb.AppendLine(endpoint.Description); } // 2. Return the content as an HttpContent return new StringContent(sb.ToString(), Encoding.UTF8, "text/markdown"); } } ``` Então, basta usá-lo na sua configuração: ```csharp host.UseApiDocumentation( // ... exporter: new MyCustomExporter() ); ``` ### Full Example Abaixo está um exemplo completo demonstrando como configurar o `Sisk.Documenting` e documentar um controlador simples. ```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}!"); } } ``` Neste exemplo, acessar `/api/docs` servirá a documentação gerada para a API "My application", descrevendo o endpoint `GET /` e seu parâmetro `name`. --- # Configuração manual (avançada) Source: https://docs.sisk-framework.org/pt-br/docs/advanced/manual-setup.html Use a configuração manual quando precisar montar os componentes do servidor por conta própria, como quando um processo deve expor vários hosts, portas, roteadores ou configuração personalizada do servidor. Para a maioria das aplicações, a API de construtor é mais curta e deve ser preferida. A configuração manual é útil quando você deseja controle direto sobre as quatro peças principais: um `Router`, um ou mais objetos `ListeningHost`, um `HttpServerConfiguration` e o `HttpServer` final. Primeiro, precisamos entender o conceito de requisição/resposta. É bastante simples: para cada requisição, deve haver uma resposta. O Sisk segue esse princípio também. Vamos criar um método que responde com a mensagem "Hello, World!" em HTML, especificando o código de status e os cabeçalhos. ```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; } ``` O próximo passo é associar este método a uma rota HTTP. ## Roteadores Roteadores são abstrações de rotas de requisição e servem como ponte entre requisições e respostas para o serviço. Roteadores gerenciam rotas do serviço, funções e erros. Um roteador pode ter várias rotas, e cada rota pode executar diferentes operações naquele caminho, como executar uma função, servir uma página ou fornecer um recurso do servidor. Vamos criar nosso primeiro roteador e associar o método `IndexPage` ao caminho de índice. ```csharp Router mainRouter = new Router(); mainRouter.MapGet("/", IndexPage); ``` Agora nosso roteador pode receber requisições e enviar respostas. Contudo, `mainRouter` não está vinculado a um host ou a um servidor, portanto não funcionará por conta própria. O próximo passo é criar nosso ListeningHost. ## Hosts de escuta e portas Um [ListeningHost](https://docs.sisk-framework.org/api/Sisk.Core.Http.ListeningHost.md) pode hospedar um roteador e múltiplas portas de escuta para o mesmo roteador. Um [ListeningPort](https://docs.sisk-framework.org/api/Sisk.Core.Http.ListeningPort.md) é um prefixo onde o servidor HTTP escutará. Aqui, podemos criar um `ListeningHost` que aponta para dois endpoints para o nosso roteador: ```csharp ListeningHost myHost = new ListeningHost { Router = mainRouter, Ports = new ListeningPort[] { new ListeningPort("http://localhost:5000/") } }; ``` Agora nosso servidor HTTP escutará os endpoints especificados e redirecionará suas requisições para o nosso roteador. ## Configuração do servidor A configuração do servidor é responsável pela maior parte do comportamento do próprio servidor HTTP. Nesta configuração, podemos associar `ListeningHosts` ao nosso servidor. ```csharp HttpServerConfiguration config = new HttpServerConfiguration(); config.ListeningHosts.Add(myHost); // Adiciona nosso ListeningHost a esta configuração de servidor ``` Opções comuns de configuração do servidor: | Propriedade | Padrão | Quando usar | Observações | | --- | --- | --- | --- | | [RemoteRequestsAction](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.RemoteRequestsAction.md) | `RequestListenAction.Accept` | O serviço deve rejeitar requisições não locais, a menos que venham através de um proxy reverso confiável. | Defina como `Drop` somente quando a topologia de implantação estiver clara. | | [IncludeRequestIdHeader](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.IncludeRequestIdHeader.md) | `false` | Clientes ou proxies precisam do ID de requisição do Sisk no cabeçalho de resposta `X-Request-Id`. | Combine com logs que incluam `HttpRequest.RequestId`. | | [IdleConnectionTimeout](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.IdleConnectionTimeout.md) | `120` segundos | Conexões keep-alive ociosas devem ser fechadas mais cedo ou mais tarde. | Isso é aplicado pelo mecanismo HTTP. | | [NormalizeHeadersEncodings](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.NormalizeHeadersEncodings.md) | `false` | Você recebe cabeçalhos com incompatibilidade de codificação. | Isso tem um custo de processamento; deixe desativado a menos que seja necessário. | | [SendSiskHeader](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.SendSiskHeader.md) | `true` | Você deseja ocultar ou expor o cabeçalho `X-Powered-By` do Sisk. | Desative-o para políticas de cabeçalho de produção mais rigorosas. | | [OptionsLogMode](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.OptionsLogMode.md) | `LogOutput.Both` | Você deseja reduzir ou redirecionar logs gerados pelo tratamento automático de `OPTIONS`. | Usa os mesmos valores de modo de log que as rotas. | | [AsyncRequestProcessing](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.AsyncRequestProcessing.md) | `true` | Você precisa de processamento determinístico de requisição única para diagnóstico. | Desativá-lo limita a taxa de transferência. | | [DisposeDisposableContextValues](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.DisposeDisposableContextValues.md) | `true` | Valores do bag de requisição que implementam `IDisposable` devem ser descartados automaticamente. | Mantenha habilitado a menos que a propriedade seja gerenciada em outro lugar. | | [ConvertIAsyncEnumerableIntoEnumerable](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ConvertIAsyncEnumerableIntoEnumerable.md) | `true` | Manipuladores de valor devem receber enumeráveis assíncronos como valores enumeráveis bloqueantes. | Desative quando você implementar seu próprio tratamento de fluxo assíncrono. | | [KeepAlive](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.KeepAlive.md) | `true` | Conexões devem permanecer reutilizáveis após respostas. | Desative para clientes ou intermediários que não lidam bem com conexões persistentes. | | [ForceTrailingSlash](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ForceTrailingSlash.md) | `false` | Rotas GET devem redirecionar para uma URL com barra final. | Aplica-se apenas a rotas não regex. | | [MaximumContentLength](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.MaximumContentLength.md) | `0` | Corpos de requisição precisam de um limite de tamanho. | `0` significa ilimitado até que limites do framework ou de memória sejam atingidos. | | [EnableAutomaticResponseCompression](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.EnableAutomaticResponseCompression.md) | `false` | Respostas devem ser comprimidas automaticamente quando o cliente as suporta. | Respostas `CompressedContent` existentes não são comprimidas novamente. | Em seguida, podemos criar nosso servidor HTTP: ```csharp HttpServer server = new HttpServer(config); server.Start(); // Inicia o servidor Console.ReadKey(); // Impede que a aplicação saia ``` Agora podemos compilar nosso executável e executar nosso servidor HTTP com o comando: ```bash dotnet watch ``` Em tempo de execução, abra seu navegador e navegue até o caminho do servidor, e você deverá ver: --- # Ciclo de vida da requisição Source: https://docs.sisk-framework.org/pt-br/docs/advanced/request-lifecycle.html Abaixo é explicado todo o ciclo de vida de uma requisição por meio de um exemplo de requisição HTTP. - **Recebendo a requisição:** cada requisição cria um contexto HTTP entre a própria requisição e a resposta que será entregue ao cliente. Esse contexto provém do listener interno do Sisk, que pode ser o [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) ou [Cadente](https://blog.sisk-framework.org/posts/2025-01-29-cadente-experiment/). - Validação de requisição externa: a validação de [HttpServerConfiguration.RemoteRequestsAction](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.RemoteRequestsAction.md) é validada para a requisição. - Se a requisição for externa e a propriedade for `Drop`, a conexão é fechada sem resposta ao cliente com um `HttpServerExecutionStatus = RemoteRequestDropped`. - Configuração do Forwarding Resolver: se um [ForwardingResolver](https://docs.sisk-framework.org/pt-br/docs/advanced/forwarding-resolvers.md) estiver configurado, ele chamará o método [OnResolveRequestHost](https://docs.sisk-framework.org/api/Sisk.Core.Http.ForwardingResolver.OnResolveRequestHost.md) no host original da requisição. - Correspondência de DNS: com o host resolvido e com mais de um [ListeningHost](https://docs.sisk-framework.org/api/Sisk.Core.Http.ListeningHost.md) configurado, o servidor buscará o host correspondente para a requisição. - Se nenhum ListeningHost corresponder, uma resposta 400 Bad Request é retornada ao cliente e um status `HttpServerExecutionStatus = DnsUnknownHost` é retornado ao contexto HTTP. - Se um ListeningHost corresponder, mas seu [Router](https://docs.sisk-framework.org/api/Sisk.Core.Http.ListeningHost.Router.md) ainda não estiver inicializado, uma resposta 503 Service Unavailable é retornada ao cliente e um status `HttpServerExecutionStatus = ListeningHostNotReady` é retornado ao contexto HTTP. - Vinculação do router: o router do ListeningHost correspondente é associado ao servidor HTTP recebido. - Se o router já estiver associado a outro servidor HTTP, o que não é permitido porque o router usa ativamente os recursos de configuração do servidor, uma `InvalidOperationException` é lançada. Isso ocorre apenas durante a inicialização do servidor HTTP, não durante a criação do contexto HTTP. - Pré-definição de cabeçalhos: - Predefine o cabeçalho `X-Request-Id` na resposta se estiver configurado para isso. - Predefine o cabeçalho `X-Powered-By` na resposta se estiver configurado para isso. - Validação do tamanho do conteúdo: valida se o conteúdo da requisição é menor que [HttpServerConfiguration.MaximumContentLength](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.MaximumContentLength.md) apenas se for maior que zero. - Se a requisição enviar um `Content-Length` maior que o configurado, uma resposta 413 Payload Too Large é retornada ao cliente e um status `HttpServerExecutionStatus = ContentTooLarge` é retornado ao contexto HTTP. - O evento `OnHttpRequestOpen` é invocado para todos os manipuladores de servidor HTTP configurados. - **Roteando a ação:** o servidor invoca o router para a requisição recebida. - Se o router não encontrar uma rota que corresponda à requisição: - Se a propriedade [Router.NotFoundErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.NotFoundErrorHandler.md) estiver configurada, a ação é invocada, e a resposta da ação é encaminhada ao cliente HTTP. - Se a propriedade anterior for nula, uma resposta padrão 404 Not Found é retornada ao cliente. - Se o router encontrar uma rota correspondente, mas o método da rota não corresponder ao método da requisição: - Se a propriedade [Router.MethodNotAllowedErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.MethodNotAllowedErrorHandler.md) estiver configurada, a ação é invocada e a resposta da ação é encaminhada ao cliente HTTP. - Se a propriedade anterior for nula, uma resposta padrão 405 Method Not Allowed é retornada ao cliente. - Se a requisição for do método `OPTIONS`: - O router retorna uma resposta 200 Ok ao cliente somente se nenhuma rota corresponder ao método da requisição (o método da rota não for explicitamente [RouteMethod.Options](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouteMethod.md)). - Se a propriedade [HttpServerConfiguration.ForceTrailingSlash](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ForceTrailingSlash.md) estiver habilitada, a rota correspondida não for uma expressão regular, o caminho da requisição não terminar com `/`, e o método da requisição for `GET`: - Uma resposta HTTP 307 Temporary Redirect com o cabeçalho `Location` contendo o caminho e a query para o mesmo local com um `/` ao final é retornada ao cliente. - O evento `OnContextBagCreated` é invocado para todos os manipuladores de servidor HTTP configurados. - Todas as instâncias globais de [IRequestHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.IRequestHandler.md) com a flag `BeforeResponse` são executadas. - Se algum manipulador retornar uma resposta não nula, essa resposta é encaminhada ao cliente HTTP e o contexto é fechado. - Se um erro for lançado nesta etapa e [HttpServerConfiguration.ThrowExceptions](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ThrowExceptions.md) estiver desabilitado: - Se a propriedade [Router.CallbackErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.CallbackErrorHandler.md) estiver habilitada, ela é invocada e a resposta resultante é retornada ao cliente. - Se a propriedade anterior não estiver definida, uma resposta vazia é retornada ao servidor, que encaminha uma resposta de acordo com o tipo de exceção lançada, que normalmente é 500 Internal Server Error. - Todas as instâncias de [IRequestHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.IRequestHandler.md) definidas na rota e com a flag `BeforeResponse` são executadas. - Se algum manipulador retornar uma resposta não nula, essa resposta é encaminhada ao cliente HTTP e o contexto é fechado. - Se um erro for lançado nesta etapa e [HttpServerConfiguration.ThrowExceptions](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ThrowExceptions.md) estiver desabilitado: - Se a propriedade [Router.CallbackErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.CallbackErrorHandler.md) estiver habilitada, ela é invocada e a resposta resultante é retornada ao cliente. - Se a propriedade anterior não estiver definida, uma resposta vazia é retornada ao servidor, que encaminha uma resposta de acordo com o tipo de exceção lançada, que normalmente é 500 Internal Server Error. - A ação do router é invocada e transformada em uma resposta HTTP. - Se um erro for lançado nesta etapa e [HttpServerConfiguration.ThrowExceptions](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ThrowExceptions.md) estiver desabilitado: - Se a propriedade [Router.CallbackErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.CallbackErrorHandler.md) estiver habilitada, ela é invocada e a resposta resultante é retornada ao cliente. - Se a propriedade anterior não estiver definida, uma resposta vazia é retornada ao servidor, que encaminha uma resposta de acordo com o tipo de exceção lançada, que normalmente é 500 Internal Server Error. - Todas as instâncias globais de [IRequestHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.IRequestHandler.md) com a flag `AfterResponse` são executadas. - Se algum manipulador retornar uma resposta não nula, a resposta do manipulador substitui a resposta anterior e é imediatamente encaminhada ao cliente HTTP. - Se um erro for lançado nesta etapa e [HttpServerConfiguration.ThrowExceptions](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ThrowExceptions.md) estiver desabilitado: - Se a propriedade [Router.CallbackErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.CallbackErrorHandler.md) estiver habilitada, ela é invocada e a resposta resultante é retornada ao cliente. - Se a propriedade anterior não estiver definida, uma resposta vazia é retornada ao servidor, que encaminha uma resposta de acordo com o tipo de exceção lançada, que normalmente é 500 Internal Server Error. - Todas as instâncias de [IRequestHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.IRequestHandler.md) definidas na rota e com a flag `AfterResponse` são executadas. - Se algum manipulador retornar uma resposta não nula, a resposta do manipulador substitui a resposta anterior e é imediatamente encaminhada ao cliente HTTP. - Se um erro for lançado nesta etapa e [HttpServerConfiguration.ThrowExceptions](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ThrowExceptions.md) estiver desabilitado: - Se a propriedade [Router.CallbackErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.CallbackErrorHandler.md) estiver habilitada, ela é invocada e a resposta resultante é retornada ao cliente. - Se a propriedade anterior não estiver definida, uma resposta vazia é retornada ao servidor, que encaminha uma resposta de acordo com o tipo de exceção lançada, que normalmente é 500 Internal Server Error. - **Processando a resposta:** com a resposta pronta, o servidor a prepara para envio ao cliente. - Os cabeçalhos da Política de Compartilhamento de Recursos entre Origens (CORS) são definidos na resposta de acordo com o que foi configurado no atual [ListeningHost.CrossOriginResourceSharingPolicy](https://docs.sisk-framework.org/api/Sisk.Core.Http.ListeningHost.CrossOriginResourceSharingPolicy.md). - O código de status e os cabeçalhos da resposta são enviados ao cliente. - O conteúdo da resposta é enviado ao cliente: - Se o conteúdo da resposta for descendente de [ByteArrayContent](https://learn.microsoft.com/en-us/dotnet/api/system.net.http.bytearraycontent), os bytes da resposta são copiados diretamente para o fluxo de saída da resposta. - Se a condição anterior não for atendida, a resposta é serializada para um fluxo e copiada para o fluxo de saída da resposta. - Os fluxos são fechados e o conteúdo da resposta é descartado. - Se [HttpServerConfiguration.DisposeDisposableContextValues](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.DisposeDisposableContextValues.md) estiver habilitado, todos os objetos definidos no contexto da requisição que herdam de [IDisposable](https://learn.microsoft.com/en-us/dotnet/api/system.idisposable) são descartados. - O evento `OnHttpRequestClose` é invocado para todos os manipuladores de servidor HTTP configurados. - Se uma exceção foi lançada no servidor, o evento `OnException` é invocado para todos os manipuladores de servidor HTTP configurados. - Se a rota permitir registro de acesso e [HttpServerConfiguration.AccessLogsStream](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.AccessLogsStream.md) não for nulo, uma linha de log é escrita na saída de log. - Se a rota permitir registro de erros, houver uma exceção, e [HttpServerConfiguration.ErrorsLogsStream](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ErrorsLogsStream.md) não for nulo, uma linha de log é escrita na saída de log de erros. - Se o servidor estiver aguardando uma requisição através de [HttpServer.WaitNext](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServer.WaitNext.md), o mutex é liberado e o contexto fica disponível para o usuário. --- # Resolvedores de Encaminhamento Source: https://docs.sisk-framework.org/pt-br/docs/advanced/forwarding-resolvers.html Um Resolvedor de Encaminhamento é um auxiliar que ajuda a decodificar informações que identificam o cliente por meio de uma requisição, proxy, CDN ou balanceadores de carga. Quando seu serviço Sisk roda através de um proxy reverso ou direto, o endereço IP, host e protocolo do cliente podem ser diferentes da requisição original, pois há um encaminhamento de um serviço para outro. Essa funcionalidade do Sisk permite que você controle e resolva essas informações antes de trabalhar com a requisição. Esses proxies geralmente fornecem cabeçalhos úteis para identificar seu cliente. Atualmente, com a classe [ForwardingResolver](https://docs.sisk-framework.org/api/Sisk.Core.Http.ForwardingResolver.md) é possível resolver o endereço IP do cliente, o host e o protocolo HTTP usado. A partir da versão 1.0 do Sisk, o servidor não possui mais uma implementação padrão para decodificar esses cabeçalhos por motivos de segurança que variam de serviço para serviço. Por exemplo, o cabeçalho `X-Forwarded-For` inclui informações sobre os endereços IP que encaminharam a requisição. Esse cabeçalho é usado por proxies para transportar uma cadeia de informações até o serviço final e inclui o IP de todos os proxies utilizados, inclusive o endereço real do cliente. O problema é: às vezes é difícil identificar o IP remoto do cliente e não há uma regra específica para identificar esse cabeçalho. É altamente recomendável ler a documentação dos cabeçalhos que você está prestes a implementar abaixo: - Leia sobre o cabeçalho `X-Forwarded-For` [aqui](https://developer.mozilla.org/en-US/docs/pt-br/Web/HTTP/Headers/X-Forwarded-For#security_and_privacy_concerns). - Leia sobre o cabeçalho `X-Forwarded-Host` [aqui](https://developer.mozilla.org/en-US/docs/pt-br/Web/HTTP/Headers/X-Forwarded-Host). - Leia sobre o cabeçalho `X-Forwarded-Proto` [aqui](https://developer.mozilla.org/en-US/docs/pt-br/Web/HTTP/Headers/X-Forwarded-Proto). ## A classe ForwardingResolver Esta classe possui três métodos virtuais que permitem a implementação mais adequada para cada serviço. Cada método é responsável por resolver informações da requisição através de um proxy: o endereço IP do cliente, o host da requisição e o protocolo de segurança usado. Por padrão, o Sisk sempre usará as informações da requisição original, sem resolver nenhum cabeçalho. O exemplo abaixo mostra como essa implementação pode ser usada. Ele resolve o IP do cliente por meio do cabeçalho `X-Forwarded-For` e lança um erro quando mais de um IP foi encaminhado na requisição. > [!IMPORTANT] > Não use este exemplo em código de produção. Sempre verifique se a implementação é adequada para uso. Leia a documentação dos cabeçalhos antes de implementá‑los. ```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]); } } } ``` --- # Manipuladores de servidor HTTP Source: https://docs.sisk-framework.org/pt-br/docs/advanced/http-server-handlers.html Na versão 0.16 do Sisk, introduzimos a classe `HttpServerHandler`, que tem como objetivo estender o comportamento geral do Sisk e fornecer manipuladores de eventos adicionais ao Sisk, como tratamento de requisições HTTP, roteadores, sacos de contexto e muito mais. A classe concentra eventos que ocorrem durante a vida útil de todo o servidor HTTP e também de uma requisição. O protocolo HTTP não possui sessões e, portanto, não é possível preservar informações de uma requisição para outra. O Sisk, por enquanto, oferece uma forma de você implementar sessões, contextos, conexões de banco de dados e outros provedores úteis para auxiliar seu trabalho. Consulte [esta página](https://docs.sisk-framework.org/api/Sisk.Core.Http.Handlers.HttpServerHandler.md) para ler onde cada evento é disparado e qual é seu propósito. Você também pode visualizar o [ciclo de vida de uma requisição HTTP](https://docs.sisk-framework.org/pt-br/docs/advanced/request-lifecycle.md) para entender o que acontece com uma requisição e onde os eventos são disparados. O servidor HTTP permite que você use múltiplos manipuladores ao mesmo tempo. Cada chamada de evento é síncrona, ou seja, bloqueará a thread atual para cada requisição ou contexto até que todos os manipuladores associados àquela função sejam executados e concluídos. Ao contrário dos RequestHandlers, eles não podem ser aplicados a alguns grupos de rotas ou rotas específicas. Em vez disso, são aplicados a todo o servidor HTTP. Você pode aplicar condições dentro do seu Http Server Handler. Além disso, singletons de cada HttpServerHandler são definidos para cada aplicação Sisk, de modo que apenas uma instância por `HttpServerHandler` é criada. Um exemplo prático de uso do HttpServerHandler é descartar automaticamente uma conexão de banco de dados ao final da requisição. ```cs // DatabaseConnectionHandler.cs public class DatabaseConnectionHandler : HttpServerHandler { protected override void OnHttpRequestClose(HttpServerExecutionResult result) { var requestBag = result.Request.Context.RequestBag; // verifica se a requisição definiu um DbContext // em seu saco 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()); } } ``` Com o código acima, a extensão `GetDbContext` permite que um contexto de conexão seja criado diretamente a partir do objeto HttpRequest. Uma conexão não descartada pode causar problemas ao operar com o banco de dados, por isso ela é encerrada em `OnHttpRequestClose`. Você pode registrar um manipulador em um servidor HTTP no seu builder ou diretamente com [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(); } } ``` Com isso, a classe `UsersController` pode usar o contexto de banco de dados da seguinte forma: ```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("Usuário adicionado."); } } ``` O código acima usa métodos como `JsonOk` e `JsonMessage` que são incorporados ao `ApiController`, que herda de um `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 })); } } ``` Desenvolvedores podem implementar sessões, contextos e conexões de banco de dados usando esta classe. O código fornecido demonstra um exemplo prático com o `DatabaseConnectionHandler`, automatizando o descarte da conexão de banco de dados ao final de cada requisição. A integração é simples, com os manipuladores registrados durante a configuração do servidor. A classe `HttpServerHandler` oferece um conjunto de ferramentas poderoso para gerenciar recursos e estender o comportamento do Sisk em aplicações HTTP. --- # Múltiplos hosts de escuta por servidor Source: https://docs.sisk-framework.org/pt-br/docs/advanced/multi-host-setup.html O Sisk Framework sempre suportou o uso de mais de um host por servidor, ou seja, um único servidor HTTP pode escutar em várias portas e cada porta tem seu próprio roteador e seu próprio serviço em execução. Dessa forma, é fácil separar responsabilidades e gerenciar serviços em um único servidor HTTP com o Sisk. O exemplo abaixo mostra a criação de dois ListeningHosts, cada um escutando em uma porta diferente, com roteadores e ações distintas. Leia [manually creating your app](https://docs.sisk-framework.org/pt-br/docs/advanced/manual-setup.md) para entender os detalhes sobre essa abstração. ```cs static void Main(string[] args) { // cria dois hosts de escuta, cada um com seu próprio roteador e // escuta em sua própria porta // 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!")); // cria uma configuração de servidor e adiciona ambos // os hosts de escuta nela // HttpServerConfiguration configuration = new HttpServerConfiguration(); configuration.ListeningHosts.Add(hostA); configuration.ListeningHosts.Add(hostB); // cria um servidor HTTP que usa a // configuração especificada // HttpServer server = new HttpServer(configuration); // inicia o 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/pt-br/docs/advanced/server-engines.html O Sisk Framework é dividido em vários pacotes, onde o principal (Sisk.HttpServer) não inclui um servidor HTTP base - por padrão, o [HttpListener](https://learn.microsoft.com/en-us/dotnet/api/system.net.httplistener?view=net-9.0) é usado como o principal motor do Sisk para realizar o papel de baixo nível do servidor. O motor HTTP cumpre o papel da camada abaixo da camada de aplicação oferecida pelo Sisk. Essa camada é responsável pelo gerenciamento de conexões, serialização e deserialização de mensagens, controle de fila de mensagens e comunicação com o soquete da máquina. A classe [HttpServerEngine](https://docs.sisk-framework.org/api/Sisk.Core.Http.Engine.HttpServerEngine.md) expõe uma API para implementar todas as funcionalidades necessárias de um motor HTTP para ser usado em camadas superiores com o Sisk, como roteamento, SSE, middlewares, etc. Essas funções não são responsabilidade do motor HTTP, mas sim de um subconjunto de bibliotecas que utilizarão o motor HTTP como base para execução. Com essa abstração, é possível portar o Sisk para ser usado com qualquer outro motor HTTP, escrito em .NET ou não, como o Kestrel, por exemplo. Atualmente, o Sisk permanece usando uma abstração do nativo [HttpListener](https://learn.microsoft.com/en-us/dotnet/api/system.net.httplistener?view=net-9.0) do .NET como padrão para novos projetos. Essa abstração padrão traz alguns problemas específicos, como comportamento não especificado em diferentes plataformas (o HttpListener tem uma implementação para Windows e outra para outras plataformas), falta de suporte a SSL e desempenho não muito agradável fora do Windows. Uma implementação experimental de um servidor de alto desempenho escrito puramente em C# também está disponível como um motor HTTP para o Sisk, chamado de projeto [Cadente](https://github.com/sisk-http/core/tree/main/cadente), que é um experimento de um servidor gerenciado que pode ser usado com o Sisk ou não. ## Implementando um Motor HTTP para o Sisk Você pode criar uma ponte de conexão entre um servidor HTTP existente e o Sisk estendendo a classe [HttpServerEngine](https://docs.sisk-framework.org/api/Sisk.Core.Http.Engine.HttpServerEngine.md). Além dessa classe, você também terá que implementar abstrações para contextos, solicitações e respostas. Um exemplo completo de abstração está [disponível no GitHub](https://github.com/sisk-http/core/blob/main/src/Http/Engine/HttpListenerAbstractEngine.cs) para visualização. Ele parece com isso: ```csharp /// /// Fornece uma implementação de usando . /// public sealed class HttpListenerAbstractEngine : HttpServerEngine { private HttpListener _listener; private static Lazy shared = new Lazy ( () => new HttpListenerAbstractEngine () ); /// /// Obtém a instância compartilhada da classe . /// public static HttpListenerAbstractEngine Shared => shared.Value; /// /// Inicializa uma nova instância da classe . /// public HttpListenerAbstractEngine () { _listener = new HttpListener { IgnoreWriteExceptions = true }; } /// public override TimeSpan IdleConnectionTimeout { get => _listener.TimeoutManager.IdleConnection; set => _listener.TimeoutManager.IdleConnection = value; } // ... } ``` ## Escolhendo um Loop de Eventos Durante a criação de um motor HTTP, o servidor irá ouvir solicitações em um loop e criar contextos para lidar com cada uma delas em threads separados. Para isso, você terá que escolher um [HttpServerEngineContextEventLoopMechanism](https://docs.sisk-framework.org/api/Sisk.Core.Http.Engine.HttpServerEngineContextEventLoopMechanism.md): - `InlineAsynchronousGetContext` o loop de eventos é linear - as chamadas de tratamento de contexto HTTP ocorrem em um loop assíncrono. - `UnboundAsynchronousGetContext` o loop de eventos é transmitido através dos métodos `BeginGetContext` e `EndGetContext`. ```csharp public override HttpServerEngineContextEventLoopMechanism EventLoopMechanism => HttpServerEngineContextEventLoopMechanism.UnboundAsynchronousGetContext; ``` Você não precisa implementar ambos os loops de eventos. Escolha o que mais faz sentido para o seu motor HTTP. ## Testes Após vincular o seu motor HTTP, é essencial realizar testes para garantir que todas as funcionalidades do Sisk tenham um comportamento idêntico ao usar outros motores. **É extremamente importante** ter o mesmo comportamento do Sisk para diferentes motores HTTP. Você pode visitar o repositório de testes no [GitHub](https://github.com/sisk-http/core/tree/main/tests).