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

sprays architecture makes the creation of custom directives really easy. When you look at the implementation of the existing directives you'll see that most of them are themselves put together from a rather limited set of low-level directives that implement the very basic directive operations "filtering" and "transforming".

The filter Directives

For implementing a directive that filters requests based on some criterion and/or extracts some values you should use one the following directives as a basis:

  • filter
  • filter1
  • filter2
  • filter3
  • filter4
  • filter5
  • filter6
  • filter7
  • filter8
  • filter9

Each of these directives takes one argument of type RouteFilter, which is defined like this:

type RouteFilter[T <: Product] = RequestContext => FilterResult[T]

There are only two types that implement a FilterResult: Reject and Pass. By returning either one or the other the filter directive decides whether to reject the request or not. Should the RouteFilter function decide to let the request pass it can optionally supply the Pass instance a number of extraction objects. The number of extractions must be stable (i.e. independent from the request) and match the filter method the RouteFilter is passed to. filter takes a RouteFilter that produces zero values, filter1 one that produces 1 value, and so on. There are no restrictions on the types of the extracted values, the filter infrastructure will make sure that they are properly and type-safely passed to the inner function producing a Route.

As an example, here is a slightly simplified version of the method directive:

def method(m: HttpMethod) = filter { ctx =>
  if (ctx.request.method == m) Pass() else Reject(MethodRejection(m)) 
}

In addition to the extractions the Pass class can optionally be supplied with a custom RequestContext transformation function, which allows you to combine "filtering" with "transforming".

The transform... Directives

Apart from filtering requests directives need a way to transform the incoming request, the outgoing response or both. Transforming requests can easily be created via the following built-in transformation directives:

  • transformRequest
  • transformResponse
  • transformRequestContext
  • transformRejections
  • transformRoute
  • transformChunkedResponse
  • transformUnchunkedResponse

The most basic ones are transformRoute and transformRequestContext, the others are merely small specializations. transformRoute applies a given Route => Route function to its inner route. This lets you wrap an inner route with your own logic. For example, the cacheResults directive is written using transformRoute since it needs to be able to explicitly run its inner route in the context of its own logic.

transformRequestContext is the most basic directive for all logic that does not explicitly need to reference its inner route. It takes a RequestContext => RequestContext function as parameter and allows you to inject logic before and/or after the inner route is run.

As examples, here are the implementation of two other transformation directives, which directly rely on transformRequestContext:

def transformRequest(f: HttpRequest => HttpRequest) =
  transformRequestContext(_.withRequestTransformed(f))

def transformResponse(f: HttpResponse => HttpResponse) =
  transformRequestContext(_.withHttpResponseTransformed(f))

For an example of a custom route that uses transformResponse you might want to take a look at the "markdown-server" example (see the Example Projects chapter), which transforms responses with the custom media type text/x-markdown to text/html.

Clone this wiki locally