Resources

genhttp-kt 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, the classpath, 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.

Kotlin
val resource = Resource.fromFile("/var/www/downloads/myvideo.mp4") // or fromString, fromClasspath, ...

val download = Asset.from(resource).asDownload()

Host.create()
    .handler(download)
    .run()

By implementing the Resource interface, a custom data source can be used to provide resources, for example a database or a cloud blob storage:

Kotlin
import org.genhttp.api.protocol.ContentType
import org.genhttp.api.protocol.ResponseSink

import java.io.InputStream
import java.time.Instant

interface Resource {

    val name: String?

    val modified: Instant?

    val contentType: ContentType?

    val length: Long?

    suspend fun calculateChecksum(): Long

    suspend fun getContent(): InputStream

    suspend fun write(sink: ResponseSink)

}

write has no default implementation and must write the resource’s content to the given sink; getContent is only needed if other code wants a plain InputStream 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:

ExampleDescription
Resource.fromString("Hello World")Creates a resource from a string constant.
Resource.fromFile("binary.blob")Creates a resource from the given file (a path or a java.nio.file.Path).
Resource.fromClasspath("web/binary.blob")Loads the resource from the classpath.
Resource.fromWeb("https://...")Creates a resource to be fetched from a web server.

On the JVM, files that are packaged with your application - such as the content of src/main/resources in a Gradle or Maven project - are found on the classpath instead of in an assembly as in GenHTTP. They can be loaded in two ways:

Kotlin
// an absolute path on the classpath (the leading slash is optional)
val index = Resource.fromClasspath("web/index.html")

// relative to the package of the given class, like Class.getResource()
val template = Resource.fromClasspath(Template::class, "template.html")

class Template

The first variant uses the context class loader of the current thread; you can pass another one as the second argument. As classpath resources are read from directories while you develop and from JAR files once your application is packaged, both variants work the same in your IDE and in production.

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 renderer will re-compile its template whenever the underlying resource has changed.

Kotlin
val resource = Resource.fromFile("...")
    .build()
    .track()

resource.getContent().use { content ->
    // ...
}

val changed = resource.checkChanged()

If you have not built the resource yet, buildWithTracking() combines both steps:

Kotlin
val 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.

Kotlin
val tree = ResourceTree.fromDirectory("/var/www/downloads/") // or fromClasspath, ...

val listing = Listing.from(tree)

Host.create()
    .handler(listing)
    .run()

ResourceTree.fromClasspath("web") provides a directory on the classpath as a tree. It combines the content of this directory from all roots of the classpath - the folders of your project as well as every JAR file - so a tree contains the same files when you run your application from the IDE and when it is packaged. ResourceTree.fromClasspath(MyType::class, "web") resolves the directory relative to the package of the given class.

Archives

This module allows you to create resource trees from archive files: ZIP, TAR (optionally compressed with gzip or bzip2) and 7z, read with Apache Commons Compress. 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:

Kotlin
import org.genhttp.api.content.io.Resource
import org.genhttp.engine.internal.Host

import org.genhttp.modules.archives.*
import org.genhttp.modules.directorybrowsing.*
import org.genhttp.modules.io.*
import org.genhttp.modules.practices.*

// http://localhost:8080

suspend fun main() {
    val archive = Resource.fromFile("./sample.zip")

    val tree = ArchiveTree.from(archive)

    val app = Listing.from(tree)

    Host.create()
        .handler(app)
        .defaults()
        .run()
}

Unlike GenHTTP, genhttp-kt does not read RAR archives.

Virtual Trees

Virtual trees allow to combine different sources of resources into a unified tree:

Kotlin
val tree = VirtualTree.create()
    .add("index.html", Resource.fromFile("./index.html"))
    .add("config.js", Resource.fromClasspath("web/config.js"))
    .add("dist", ResourceTree.fromDirectory("./dist"))

val 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 find() 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.

Kotlin
suspend fun resolve(tree: ResourceTree, request: Request) {
    // either folder or file will be set
    // if both are null, routing did not succeed
    val (folder, file) = tree.find(request.header.target)
}

A custom ResourceContainer (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 compare the raw segment without decoding it first - or decode it, if it needs the name:

Kotlin
import org.genhttp.api.content.io.Resource
import org.genhttp.api.content.io.ResourceNode
import org.genhttp.api.content.io.ResourceTree
import org.genhttp.api.protocol.PathSegment

import java.time.Instant

class AttachmentTree : ResourceTree {

    override val modified: Instant? = null

    override suspend fun tryGetNode(segment: PathSegment): ResourceNode? = null

    override suspend fun getNodes(): Collection<ResourceNode> = emptyList()

    override suspend fun tryGetResource(segment: PathSegment): Resource? = findInDatabase(segment.decode())

    override suspend fun getResources(): Collection<Resource> = TODO("load all attachments")

    private suspend fun findInDatabase(name: String): Resource? = TODO("load the attachment with the given name")

}