Handlers
Handlers are responsible for analyzing HTTP requests and to serve responses to the connected client. All features available in the SDK are either provided by handlers or concerns.
If you would like to add an additional feature that cannot be achieved using the existing functionality to the SDK or your own web application, you may implement a custom handler instance. The following example will generate a simple text response:
import org.genhttp.api.content.Handler
import org.genhttp.api.protocol.ContentType
import org.genhttp.engine.internal.Host
import org.genhttp.modules.io.*
suspend fun main() {
val customHandler = Handler.from { request ->
request.respond()
.content("Hello World", ContentType.TextPlain)
.build()
}
Host.create()
.handler(customHandler)
.run()
}This code uses the Handler.from shortcut provided by the IO module (genhttp-modules-io) to reduce the
boilerplate required to implement a handler class. The lambda is a suspend function that receives the
request and returns the response, so it may call other suspending functions (such as a database client)
without blocking a thread. The full version of the example above would look like this:
import org.genhttp.api.content.ConcernBuilder
import org.genhttp.api.content.Concerns
import org.genhttp.api.content.Handler
import org.genhttp.api.content.HandlerBuilder
import org.genhttp.api.infrastructure.Server
import org.genhttp.api.protocol.ContentType
import org.genhttp.api.protocol.Request
import org.genhttp.api.protocol.Response
import org.genhttp.engine.internal.Host
import org.genhttp.modules.io.content
suspend fun main() {
Host.create()
.handler(CustomHandlerBuilder())
.run()
}
class CustomHandler : Handler {
override suspend fun prepare(server: Server) {
// perform CPU or I/O heavy work to initialize this
// handler and its children
}
override suspend fun handle(request: Request): Response? =
request.respond()
.content("Hello World", ContentType.TextPlain)
.build()
}
class CustomHandlerBuilder : HandlerBuilder.WithConcerns<CustomHandlerBuilder> {
private val concerns = mutableListOf<ConcernBuilder>()
override fun add(concern: ConcernBuilder): CustomHandlerBuilder {
concerns.add(concern)
return this
}
override fun build(): Handler = Concerns.chain(concerns, CustomHandler())
}A handler implements two functions, both of them suspend:
prepare(server)is invoked when the server starts, before it accepts the first requests, so that expensive initialization does not slow down the first clients. A handler that wraps other handlers (such as a layout) relays the call to them, so the whole handler chain gets prepared. Depending on the setup, it may be called more than once.handle(request)is invoked for every request routed to the handler. Returningnullmeans that the handler has no content for the request: the server answers with404 Not Found, unless the handler is one of the fallbacks of a layout, which then tries the next one.
The builder allows concerns (such as compression or CORS) to be added to the handler. Concerns.chain(...)
wraps the handler into the concerns that have been added and returns the outermost handler of the resulting chain,
which is the one passed to the server.
GenHTTP declares builders as IHandlerBuilder<TBuilder>. Kotlin cannot overload an interface name by the number of
its type parameters, so genhttp-kt has HandlerBuilder for any builder of a handler and HandlerBuilder.WithConcerns<TBuilder>
for builders that accept concerns. Host.handler(...) and Layout.add(...) accept either a handler or its builder.
As handlers are invoked for every request handled by the server, it is usually worth it to
optimize them for performance. For example, as the content served by our CustomHandler does
not change depending on the request, the string resource instance could be cached by a property
initialized in the constructor.