Header Filters

§Header Filters

In Lagom you may add HeaderFilters 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 HeaderFilters 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

Found an error in this documentation? The source code for this page can be found here. Please feel free to edit and contribute a pull request.