Byrone.Xenia 0.1.1

dotnet add package Byrone.Xenia --version 0.1.1
                    
NuGet\Install-Package Byrone.Xenia -Version 0.1.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Byrone.Xenia" Version="0.1.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Byrone.Xenia" Version="0.1.1" />
                    
Directory.Packages.props
<PackageReference Include="Byrone.Xenia" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Byrone.Xenia --version 0.1.1
                    
#r "nuget: Byrone.Xenia, 0.1.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Byrone.Xenia@0.1.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Byrone.Xenia&version=0.1.1
                    
Install as a Cake Addin
#tool nuget:?package=Byrone.Xenia&version=0.1.1
                    
Install as a Cake Tool

Xenia

<img src="../assets/logo.png" alt="Xenia Logo">

A simple, minimalistic HTTP/1.1 server written in C#.

Quickstart

using System.Threading;
using System.Net.Sockets;
using Byrone.Xenia;
using Byrone.Xenia.Utilities;

// Binds to: 0.0.0.0:6969
var config = new Config(IPv4.Local, 6969);

var server = new Server(config, RequestHandler);

// Create a background thread to accept clients on.
var thread = new Thread(server.Run)
{
	IsBackground = true,
	Name = "Xenia Server Thread",
};
thread.Start();

while (true)
{
	if (System.Console.ReadKey().Key == System.ConsoleKey.C)
	{
		break;
	}
}

// It's important to clean up and release your resources in this exact order:
// 1. Dispose the server. This will stop the server from accepting new clients.
// 2. 'Join' the thread (terminate the background thread).
// Joining a thread is a blocking call, and not disposing the server before joining the thread...
// ...will result in the thread waiting for a new client, and THEN stopping the thread.

server.Dispose();

thread.Join();

return;

// This callback gets called everytime a new request has been received and parsed.
// It requires you to return a model that implements `IResponse`, that will write a response to the connected client's stream.
static IResponse RequestHandler(in Request request)
{
	return new Response();
}

// Xenia will automatically dispose your custom repsonse,
// if you implement the `System.IDisposable` interface.
internal readonly struct Response : IResponse
{
	public void Send(Socket client, in Request request)
	{ 
		// Send a basic HTTP and HTML response
		var response = """ 
				HTTP/1.1 200 OK
				Content-Type: text/html

				<html><body><h1>Hello world!</h1></body></html>
				"""u8;

		client.Send(response);
	}
}

How Xenia works

Xenia only handles accepting clients and receiving + parsing the HTTP request the accepted client sends. You as the developer are in control over how you handle responding to the client. You do this by implementing the IResponse interface. This interface requires you to implement a public void Send(Socket client, in Request request) function. Here you can send an HTTP response in whichever way you like.

Only have a few routes that you want to handle? A simple if-statement would probably be enough, no need to implement some specific and overcomplicated routing solution. Have a lot of (dynamic) routes? Using the RouteCollection and RouteParameters structs you can easily implement your own advanced routing system.

What about async?

Xenia attempts to use as little async/await as possible, as C#'s async system has quite some overhead. This also forces you to use this pattern, even if you don't want to. This is why (currently) Xenia is completely synchronous. It is up to you to decide how you'd like to handle things like threading and parallelism.

Another downside of the async/await system is the lack of proper System.Span<T> and System.ReadOnlySpan<T> support. While this is definitely getting better since .NET 9, we're still not quite there yet, and possibly won't be, as Spans can be (and most of the time are) allocated on the HEAP, and since async methods are ran on a different thread, possibly at a different time, there's a good chance this data is already gone.

Utilities

RouteColletion

The RouteCollection struct contains a list of predefined routes that you'll want to handle. It also provides a helper function to see if a requested path matches a predefined route.

using Byrone.Xenia;
using Byrone.Xenia.Utilities;

static IResponse RequestHandler(in Request request)
{
	var routes = new RouteCollection([
		"/"u8,
		"/blog"u8,
		"/blog/{post}"u8, // Matches with for example `/blog/my-first-post`
	]);

	if (routes.TryFind(request.Path, out var pattern))
	{
		// Send back some data based on the route by, for example,
		// calling a callback function based on the pattern
	} else {
		// No matching pattern found, show 404
	}
}

RouteParameters

By giving this struct a pattern and path, you can retrieve dynamic variables based on the requested path.

using Byrone.Xenia;
using Byrone.Xenia.Utilities;

static IResponse RequestHandler(in Request request)
{
	var routes = new RouteCollection([
		"/blog/{post}"u8,
	]);

	if (routes.TryFind(request.Path, out var pattern))
	{
		// Pattern will be: /blog/{post}
		// Path is: /blog/my-first-post

		var @params = new RouteParameters(pattern, request.Path);
		var post = @params.Get("post"u8); // Will return: my-first-post
		var id = @params.Get<int>("post"u8); // You can also parse parameters to value types
	} else {
		// No matching pattern found, show 404
	}
}

QueryParameters

Sometimes, you'd like to use parameters using a query string. Xenia also has a helper struct for this.

using Byrone.Xenia;
using Byrone.Xenia.Utilities;

static IResponse RequestHandler(in Request request)
{
	// Assuming the request path is something like: /page?id=1&action=edit
	var @params = QueryParameters.FromUrl(request.Path);

	var id = @params.Get<int>("id"u8); // Will return: 1
	var action = @params.Get("action"u8); // Will return: edit
}

Parsing parameters

Both the RouteParameters and QueryParameters can automatically parse the parameter's value into a specified type ( this includes custom types). This type must implement System.IUtf8SpanParsable<T>, however.

Most built-in (value) types implement this interface, however not every type does. Both System.DateOnly and System.DateTime are examples of types that don't implement this interface, mainly due to the wide variety of different formats these types can be serialized as. Xenia provides the Date and DateTime structs in the Byrone.Xenia.Utilities namespace that function exactly like the built-in System.DateOnly and System.DateTime types, but implement the System.IUtf8SpanParsable<T> interface so you can use date(times) in your parameters.

using Byrone.Xenia.Utilities;

// ...

var @params = new QueryParameters(query);

var createdAt = @params.Get<DateTime>("created_at"u8);
var birthDay = @params.Get<Date>("birth_day"u8);

Logging

Xenia is able to log informational output. As everyone likes to log information differently, and not everything, you have to implement your own logging solution. This is not required, however. Here's a simple example that logs to the standard C# console:

using Byrone.Xenia;

internal sealed class ConsoleLogger : ILogger
{
	public void LogDebug(scoped System.ReadOnlySpan<byte> message)
	{
		System.Console.Out.Write("[Server] DEBUG: ");
		System.Console.Out.WriteLine(ConsoleLogger.Str(message));
	}

	public void LogInfo(scoped System.ReadOnlySpan<byte> message)
	{
		System.Console.Out.Write("[Server] INFO: ");
		System.Console.Out.WriteLine(ConsoleLogger.Str(message));
	}

	public void LogWarning(scoped System.ReadOnlySpan<byte> message)
	{
		System.Console.Out.Write("[Server] WARNING: ");
		System.Console.Out.WriteLine(ConsoleLogger.Str(message));
	}

	public void LogError(scoped System.ReadOnlySpan<byte> message)
	{
		System.Console.Error.Write("[Server] ERROR: ");
		System.Console.Error.WriteLine(ConsoleLogger.Str(message));
	}

	public void LogException(System.Exception ex)
	{
		System.Console.Error.Write("[Server] EXCEPTION: ");
		System.Console.Error.WriteLine(ex);
	}

	private static string Str(scoped System.ReadOnlySpan<byte> message) =>
		System.Text.Encoding.UTF8.GetString(message);
}

// Usage:

var config = new Config(IPv4.Local, 6969)
{
	Logger = new ConsoleLogger(),
};
var server = new Server(config, RequestHandler);
Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (4)

Showing the top 4 NuGet packages that depend on Byrone.Xenia:

Package Downloads
Byrone.Xenia.Caching

Utilities for working with cached-content and applying caching headers.

Byrone.Xenia.Encoding

Helpers to find the preferred encoding from the request and encode data.

Byrone.Xenia.JSON

Helpers for working with JSON content and (de)serializing JSON using the built-in `System.Text.Json` package.

Byrone.Xenia.Data

Collection of helpers/utilities for working with different kinds of data.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.1 230 10/29/2024