# Cadente

Source: https://docs.sisk-framework.org/docs/cadente.html

Cadente is an experimental managed HTTP/1.1 listener implementation for Sisk. It serves as a replacement for the default `System.Net.HttpListener`, offering greater control and flexibility, especially on non-Windows platforms.

## Overview

By default, Sisk uses `HttpListener` (from `System.Net`) as its underlying HTTP server engine. While `HttpListener` is stable and performant on Windows (where it uses the kernel-mode HTTP.sys driver), its implementation on Linux and macOS is managed and historically has had limitations, such as lack of native SSL support (requiring a reverse proxy like Nginx) and varying performance characteristics.

Cadente aims to solve these issues by providing a fully managed HTTP/1.1 server written in C#. Its key goals are:

- **Native SSL Support:** Works on all platforms without needing external proxies or complex configuration.
- **Cross-Platform Consistency:** Identical behavior on Windows, Linux, and macOS.
- **Performance:** Designed to be a high-performance alternative to the managed `HttpListener`.
- **Independence:** Decoupled from `System.Net.HttpListener`, insulating Sisk from potential future deprecations or lack of maintenance of that component in .NET.

> [!WARNING]
> **Experimental Status**
> 
> Cadente is currently in an experimental stage (Beta). It is not yet recommended for critical production environments. The API and behavior may change.

## Installation

Cadente is available as a separate package. To use it with Sisk, you need the `Sisk.Cadente.CoreEngine` package.

```bash
dotnet add package Sisk.Cadente.CoreEngine
```

`Sisk.Cadente` and `Sisk.Cadente.CoreEngine` target .NET 9 and .NET 10. They cannot be used in projects that target .NET 8, even though `Sisk.HttpServer` supports it.

## Using with Sisk

To use Cadente as the HTTP engine for your Sisk application, you need to configure the `HttpServer` to use `CadenteHttpServerEngine` instead of the default engine.

The `CadenteHttpServerEngine` adapts the Cadente `HttpHost` to the `HttpServerEngine` abstraction required by Sisk.

```csharp
using Sisk.Core.Http;
using Sisk.Cadente.CoreEngine;

using var host = HttpServer.CreateBuilder()
    .UseEngine<CadenteHttpServerEngine>()
    .UseSsl(certificate: CertificateHelper.CreateTrustedDevelopmentCertificate("localhost"))
    .Build();

await host.StartAsync();
```

When a listening host is configured with SSL, [HttpServer.ListeningPrefixes](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServer.ListeningPrefixes.md) reports its addresses with the `https` scheme. If a client sends plain HTTP to a port configured with SSL, Cadente answers with a `301 Moved Permanently` redirect to the `https` address of the same host and path.

### Advanced Configuration

You can customize the underlying `HttpHost` instance by passing a setup action to the `CadenteHttpServerEngine` constructor. This is useful for configuring timeouts or other low-level settings.

```csharp
using var engine = new CadenteHttpServerEngine(host =>
{
    // Configure client read/write timeouts
    host.TimeoutManager.ClientReadTimeout = TimeSpan.FromSeconds(30);
    host.TimeoutManager.ClientWriteTimeout = TimeSpan.FromSeconds(30);
});
```

## Standalone Usage

Although primarily designed for Sisk, Cadente can be used as a standalone HTTP server (similar to `HttpListener`).

```csharp
using Sisk.Cadente;

var host = new HttpHost(15000)
{
    Handler = new MyHostHandler()
};

host.Start();
Thread.Sleep(-1);

class MyHostHandler : HttpHostHandler
{
    public override async Task OnContextCreatedAsync(HttpHost host, HttpHostContext context)
    {
        context.Response.StatusCode = 200;

        using var stream = await context.Response.GetResponseStreamAsync(chunked: true);
        using var writer = new StreamWriter(stream);
        await writer.WriteLineAsync("Hello, world!");
    }
}
```

When the response body is small and already in memory, `WriteInlineContent` and `WriteInlineContentAsync` write the headers and the body in a single operation. The response must have a `Content-Length` header:

```csharp
byte[] body = "Hello, world!"u8.ToArray();

context.Response.StatusCode = 200;
context.Response.Headers.Set(new HttpHeader("Content-Length", body.Length.ToString()));
await context.Response.WriteInlineContentAsync(body);
```

## Request validation

Cadente validates the request framing before it reaches the handler. Since version 1.7, it:

- supports pipelined requests on keep-alive connections;
- rejects request methods that are not valid HTTP tokens and request targets that contain bytes outside the visible ASCII range (`%x21-7E`);
- rejects requests whose `Transfer-Encoding` is anything other than a single `chunked` value, and requests with conflicting `Content-Length` headers;
- answers `431 Request Header Fields Too Large` and closes the connection when the request line and headers exceed 8 KB.

Invalid requests are answered with an error and the connection is closed, without calling the handler.
