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.
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:
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:
| Example | Description |
|---|---|
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:
// 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 TemplateThe 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.
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:
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.
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:
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:
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.
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:
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")
}