Resources
GenHTTP provides a thin abstraction layer to control access of binary resources (such as files or blobs). This allows to re-use file-dependent handlers, independent from whether the data comes from the file system, a database or somewhere else.
Resources
Resources provide a unified way to load and access binary data used by handlers to achieve their functionality. For example, the Files module serves a single resource on request - where the content of the file originates from is not important for the handler to achieve its functionality.
var resource = Resource.FromFile("/var/www/downloads/myvideo.mp4"); // or FromString, FromAssembly, ...
var download = Asset.From(resource).AsDownload();
await Host.Create()
.Handler(download)
.RunAsync();By implementing the IResource interface, a custom data source can be used to
provide resources, for example a database or a cloud blob storage:
public interface IResource
{
string? Name { get; }
DateTime? Modified { get; }
ContentType? ContentType { get; }
ulong? Length { get; }
ValueTask<ulong> CalculateChecksumAsync();
ValueTask<Stream> GetContentAsync();
ValueTask WriteAsync(IResponseSink sink);
}WriteAsync has no default implementation and must write the resource’s content to the given
sink; GetContentAsync is only needed if other code wants a plain Stream for the resource
(e.g. to pass it on to a library that expects one).
Built-in Providers
The following resource implementations are provided by the IO module:
| Example | Description |
|---|---|
Resource.FromString("Hello World") | Creates a resource from a string constant. |
Resource.FromFile("binary.blob") | Creates a resource from the given file. |
Resource.FromAssembly("binary.blob") | Loads the resource from the currently executed assembly. |
Resource.FromWeb("https://...") | Creates a resource to be fetched from a web server. |
Change Tracking
For some use cases it might be important to determine, whether a given resource has changed since it has been accessed the last time. For example, the page handler will re-compile its template whenever the underlying resource has changed.
var resource = Resource.FromFile("...")
.Build()
.Track();
await using var content = await resource.GetContentAsync();
var changed = await content.CheckChangedAsync();If you have not built the resource yet, BuildWithTracking() combines both steps:
var resource = Resource.FromFile("...")
.BuildWithTracking();Resource Trees
Similar to the resources, resource trees provide an abstraction for a directory structure that can be consumed by handlers such as the Directory Browsing or the Single Page Application.
var tree = ResourceTree.FromDirectory("/var/www/downloads/"); // or FromAssembly, ...
var listing = Listing.From(tree);
await Host.Create()
.Handler(listing)
.RunAsync();Archives
This module allows you to create resource trees from archive files such as zip, tar, 7z or rar. Typical
use cases include reducing the number of files that need to be checked into your source
repository, or directly hosting files from a release package without extracting it first.
The following example hosts an application that allows browsing the contents of the
given archive file:
using GenHTTP.Engine.Internal;
using GenHTTP.Modules.Archives;
using GenHTTP.Modules.DirectoryBrowsing;
using GenHTTP.Modules.IO;
using GenHTTP.Modules.Practices;
// http://localhost:8080
var archive = Resource.FromFile("./sample.zip");
var tree = ArchiveTree.From(archive);
var app = Listing.From(tree);
await Host.Create()
.Handler(app)
.Defaults()
.RunAsync();Virtual Trees
Virtual trees allow to combine different sources of resources into a unified tree:
var tree = VirtualTree.Create()
.Add("index.html", Resource.FromFile(...))
.Add("config.js", Resource.FromAssembly(...))
.Add("dist", ResourceTree.FromDirectory(...));
var app = SinglePageApplication.From(tree);Resolving Files
When using resource trees within a handler, you might want to search for
files based on the remaining path of the request to be routed. The FindAsync()
extension provided by the IO module will attempt to find the requested resource
or resource node from the current routing target and automatically advance it.
// either folder or file will be set
// if both are null, routing did not succeed
var (folder, file) = await Tree.FindAsync(request.Header.Target);A custom IResourceContainer (e.g. to expose a database table as a browsable tree) resolves
child nodes and resources by PathSegment rather than by string, so it can be looked up
without decoding the segment first:
public ValueTask<IResource?> TryGetResourceAsync(PathSegment segment)
{
return FindInDatabase(segment.Decode());
}