Cadente
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.
dotnet add package Sisk.Cadente.CoreEngineSisk.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.
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 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.
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).
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:
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-Encodingis anything other than a singlechunkedvalue, and requests with conflictingContent-Lengthheaders; - answers
431 Request Header Fields Too Largeand 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.