Request Contexts
A RequestContext
makes it possible to attach objects to a request whilst it is being passed down through the layers of an application.
The basic concept is that there is a global shared object which holds a bag of state (indexed by Request). This state can be modified in Filters and then that state accessed inside other Filters or the terminating HttpHandler. There are 2 available choices for manipulating this data:
- Using simple Strings to represent the keys.
- Using
RequestContextKey
s and the Lens mechanism from thehttp4k-core
module.
Whilst the first method looks technically simpler, the use of simple Strings does not provide the type-safety of the second, which uses unique shared Key objects to guarantee non-clashing of keys and type-safety of the state.
Regardless of which of the above mechanisms are used, an instance of the ServerFilters.InitialiseRequestContext
Filter must wrap the HttpHandler(s) to activate
the shared bag of state for each request, and to remove the state after the request is complete.
Gradle setup
dependencies {
implementation(platform("org.http4k:http4k-bom:5.37.1.1"))
implementation("org.http4k:http4k-core")
}
String-based keys
package content.howto.attach_context_to_a_request
import org.http4k.core.Filter
import org.http4k.core.HttpHandler
import org.http4k.core.Method.GET
import org.http4k.core.Request
import org.http4k.core.RequestContexts
import org.http4k.core.Response
import org.http4k.core.Status.Companion.OK
import org.http4k.core.then
import org.http4k.filter.ServerFilters
fun main() {
data class SharedState(val message: String)
fun AddState(contexts: RequestContexts) = Filter { next ->
{
contexts[it]["myKey"] = SharedState("hello there")
next(it)
}
}
fun PrintState(contexts: RequestContexts): HttpHandler = { request ->
val message: SharedState? = contexts[request]["myKey"]
println(message)
Response(OK)
}
// this is the shared RequestContexts object - it holds the bag of state for each request and
// tidies up afterwards
val contexts = RequestContexts()
// The first Filter is required to initialise the bag of state.
// The second Filter modifies the bag
// The handler just prints out the state
val app = ServerFilters.InitialiseRequestContext(contexts)
.then(AddState(contexts))
.then(PrintState(contexts))
app(Request(GET, "/hello"))
}
Lens-based keys
package content.howto.attach_context_to_a_request
import org.http4k.core.Filter
import org.http4k.core.HttpHandler
import org.http4k.core.Method.GET
import org.http4k.core.Request
import org.http4k.core.RequestContexts
import org.http4k.core.Response
import org.http4k.core.Status.Companion.OK
import org.http4k.core.then
import org.http4k.core.with
import org.http4k.filter.ServerFilters
import org.http4k.lens.RequestContextKey
import org.http4k.lens.RequestContextLens
fun main() {
data class SharedState(val message: String)
fun AddState(key: RequestContextLens<SharedState>) = Filter { next ->
{
// "modify" the request like any other Lens
next(it.with(key of SharedState("hello there")))
}
}
fun PrintState(key: RequestContextLens<SharedState>): HttpHandler = { request ->
// we can just extract the Lens state from the request like any other standard Lens
println(key(request))
Response(OK)
}
// this is the shared RequestContexts object - it holds the bag of state for each request and
// tidies up afterwards.
val contexts = RequestContexts()
// this Lens is the key we use to set and get the type-safe state. By using this, we gain
// typesafety and the guarantee that there will be no clash of keys.
// RequestContextKeys can be required, optional, or defaulted, as per the standard Lens mechanism.
val key = RequestContextKey.required<SharedState>(contexts)
// The first Filter is required to initialise the bag of state.
// The second Filter modifies the bag.
// The handler just prints out the state.
val app = ServerFilters.InitialiseRequestContext(contexts)
.then(AddState(key))
.then(PrintState(key))
app(Request(GET, "/hello"))
}