§Header Filters
In Lagom you may add HeaderFilter
s to your service descriptor. In a HeaderFilter
you will usually handle protocol negotiation or authentication.
A single HeaderFilter
implementation may transform a request leaving a client or entering a server and a response leaving a server and entering a client. Here’s an example that uses the User-Agent
header to read the service name:
object UserAgentHeaderFilter extends HeaderFilter {
override def transformClientRequest(request: RequestHeader): RequestHeader = {
request.principal match {
case Some(principal: ServicePrincipal) =>
request.withHeader(HeaderNames.USER_AGENT, principal.serviceName)
case _ => request
}
}
override def transformServerRequest(request: RequestHeader): RequestHeader = {
request.getHeader(HeaderNames.USER_AGENT) match {
case Some(userAgent) =>
request.withPrincipal(ServicePrincipal.forServiceNamed(userAgent))
case _ =>
request
}
}
override def transformServerResponse(
response: ResponseHeader,
request: RequestHeader
): ResponseHeader = response
override def transformClientResponse(
response: ResponseHeader,
request: RequestHeader
): ResponseHeader = response
}
This UserAgentHeaderFilter
is the default HeaderFilter
any Lagom service will use if none is specified. It uses a ServicePrincipal
which identifies the client with the service name.
In UserAgentHeaderFilter
the code at transformClientRequest
will be invoked when preparing a client invocation to add a User-Agent
header if a ServicePrincipal
was specified on the request. Note that by default, Lagom will automatically pass the current services name as the ServicePrincipal
when it makes a request. On the server end transformServerRequest
will be used to read the User-Agent
header and set that value as the request’s Principal.
Keep in mind that a header filter should only be used to deal with cross cutting protocol concerns, and nothing more. For example, you may have a header filter that describes how the current authenticated user is communicated over the HTTP protocol (by adding a user header, for example). Cross cutting domain concerns, such as authentication and validation, should not be handled in a header filter, rather they should be handled using service call composition.
§Header Filter Composition
Each service Descriptor
can only have one HeaderFilter
. In order to use several filters at once you may compose them using HeaderFilter.composite
which will return a HeaderFilter
that chains all the HeaderFilter
s you composed. When composing, the order is important so when sending message headers the filters of the composite are used in the order they were provided, and when receiving message headers the filters will be used in reverse order. So if we had the following filter:
class VerboseFilter(name: String) extends HeaderFilter {
private val log = LoggerFactory.getLogger(getClass)
def transformClientRequest(request: RequestHeader) = {
log.debug(name + " - transforming Client Request")
request
}
def transformServerRequest(request: RequestHeader) = {
log.debug(name + " - transforming Server Request")
request
}
def transformServerResponse(response: ResponseHeader, request: RequestHeader) = {
log.debug(name + " - transforming Server Response")
response
}
def transformClientResponse(response: ResponseHeader, request: RequestHeader) = {
log.debug(name + " - transforming Client Response")
response
}
}
And we registered two of them with the names Foo
and Bar
:
def descriptor = {
import Service._
named("hello")
.withCalls(
call(sayHello)
)
.withHeaderFilter(
HeaderFilter.composite(
new VerboseFilter("Foo"),
new VerboseFilter("Bar")
)
)
}
and then called the service, then we would get the following on the server output logs:
[debug] Bar - transforming Server Request
[debug] Foo - transforming Server Request
[debug] Foo - transforming Server Response
[debug] Bar - transforming Server Response