Header Filters

§Header Filters

In Lagom you may add HeaderFilters to your ServiceDescriptor. 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.

public class UserAgentHeaderFilter implements HeaderFilter {

    @Override
    public RequestHeader transformClientRequest(RequestHeader request) {
        if (request.principal().isPresent()) {
            Principal principal = request.principal().get();
            if (principal instanceof ServicePrincipal) {
                String serviceName = ((ServicePrincipal) principal).serviceName();
                return request.withHeader(Http.HeaderNames.USER_AGENT, serviceName);
            } else {
                return request;
            }
        } else {
            return request;
        }
    }

    @Override
    public RequestHeader transformServerRequest(RequestHeader request) {
        Optional<String> userAgent = request.getHeader(Http.HeaderNames.USER_AGENT);
        if (userAgent.isPresent()) {
            return request.withPrincipal(ServicePrincipal.forServiceNamed(userAgent.get()));
        } else {
            return request;
        }
    }

    @Override
    public ResponseHeader transformServerResponse(ResponseHeader response, RequestHeader request) {
        return response;
    }

    @Override
    public ResponseHeader transformClientResponse(ResponseHeader response, RequestHeader request) {
        return 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. This way a server may identify a caller client by the User-Agent.

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. 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 HTTP (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 data the filters of the composite are used in the order they were provided, and when receiving data the filters will be used in reverse order. So if we registered two verbose Filters Foo and Bar like this:

default Descriptor descriptor() {
  return named("echo").withCalls(
      namedCall("echo", this::echo)
  )
      .withHeaderFilter(HeaderFilter.composite(new FooFilter(), new BarFilter()))
      .withAutoAcl(true);

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.