Custom Directives

Part of the power of akka-http directives comes from the ease with which it’s possible to define custom directives at differing levels of abstraction.

There are essentially three ways of creating custom directives:

  1. By introducing new “labels” for configurations of existing directives
  2. By transforming existing directives
  3. By writing a directive “from scratch”

Configuration Labeling

The easiest way to create a custom directive is to simply assign a new name for a certain configuration of one or more existing directives. In fact, most of the predefined akka-http directives can be considered named configurations of more low-level directives.

The basic technique is explained in the chapter about Composing Directives, where, for example, a new directive getOrPut is defined like this:

public Route getOrPut(Supplier<Route> inner) {
  return get(inner).orElse(put(inner));
}
Route route = getOrPut(() -> complete("ok"));

Multiple directives can be nested to produce a single directive out of multiple like this:

// the composed custom directive
/**
 * @param authenticate A function returns a set of roles for the credentials of a user
 * @param inner Inner route to execute if the provided credentials has the given role
 *              if not, the request is completed with a
 */
public Route headerBasedAuth(Function<MyCredentials, Set<MyRole>> authenticate, MyRole requiredRole, Supplier<Route> inner) {
  return headerValueByName("X-My-User-Id", (userId) -> {
    return headerValueByName("X-My-User-Secret", (secret) -> {
      Set<MyRole> userRoles = authenticate.apply(new MyCredentials(userId, secret));
      if (userRoles.contains(requiredRole)) {
        return inner.get();
      } else {
        return complete(StatusCodes.FORBIDDEN, "Role " + requiredRole + " required for access");
      }
    });
  });
}
// a function for authentication
Function<MyCredentials, Set<MyRole>> authLogic =
  (credentials) -> {
    if (credentials.userId.equals("admin") && credentials.safeSecretVerification("secret"))
      return new HashSet<>(Arrays.asList(MyRole.USER, MyRole.ADMIN));
    else
      return Collections.emptySet();
  };

// and then using the custom route
Route route = get(() ->
  path("admin", () ->
    headerBasedAuth(authLogic, MyRole.ADMIN, () -> complete(StatusCodes.OK, "admin stuff"))
  )
);

Another example is the MethodDirectives which are simply instances of a preconfigured method directive. The low-level directives that most often form the basis of higher-level “named configuration” directives are grouped together in the BasicDirectives trait.

The source code for this page can be found here.