Skip to content
Mathias edited this page Mar 8, 2012 · 6 revisions

"Directives" are small building blocks of which you can construct arbitrarily complex route structures. This is a simple example of a route built from directives:

val route: Route =
  path("order" / HexIntNumber) { id =>
    get {
      completeWith {
        "Received GET request for order " + id
      }
    } ~
    put {
      completeWith {
        "Received PUT request for order " + id
      }
    }
  }

The general anatomy of a directive is as follows:

name(arguments) { extractions =>
  ... // inner route
}

It has a name, zero or more arguments and optionally an inner route. Additionally directives can "extract" a number of values and make them available to their inner routes. When seen "from the outside" a directive is an expression of type Route.

What Directives do

A directive does one or more of the following:

  • Transform the incoming RequestContext before passing it on to its inner route
  • Filter the RequestContext according to some logic, i.e. only pass on certain requests and reject all others
  • Apply some logic to the incoming RequestContext and create objects that are made available to inner routes as "extractions"
  • Complete the request

The first point deserves some more discussion. A RequestContext is the central object that is passed on through a route structure and in between actors. It's immutable but light-weight and can therefore be copied quickly. When a directive receives a RequestContext instance from the outside it can decide to pass this instance on unchanged to its inner route or it can create a copy of the RequestContext instance, with one or more changes, and pass on this copy to its inner route. Typically this is good for two things:

  • Transforming the HttpRequest instance
  • "Hooking in" another HttpResponse transformation function into the responder function chain.

For understanding the latter point it is helpful to look at what happens when, in the inner-most route of a route structure, the complete method of a RequestContext instance is called.

Consider the following hypothetical route structure of three nested directives around a simple route.

foo {
  bar {
    baz {
      completeWith {
        "Hello"
      }
    }
  }
}

Assume that foo and baz hook in response transformation logic whereas bar leaves the responder function of the RequestContext that it receives unchanged before passing it on to its inner route. This is what happens when the complete("Hello") call in the innermost route is issued:

  1. The complete method invokes the responder function of the RequestContext.
  2. The response transformation logic supplied by the baz directive runs and eventually invokes the responder function of the RequestContext the baz directive received.
  3. The response transformation logic supplied by the foo directive runs and eventually invokes the responder function of the RequestContext the foo directive received.
  4. The responder logic of the original RequestContext is run, which writes out the response and resumes the suspended request.

As you can see all response handling logic forms a function chain that directives can choose to "hook into".

Clone this wiki locally