Skip to content
This repository has been archived by the owner on Apr 24, 2024. It is now read-only.

Rejections

Johannes Rudolph edited this page Aug 14, 2013 · 6 revisions

. . . Deprecation Note

This documentation is for release 0.9.0 (from 03/2012), which is built against Scala 2.9.1 and Akka 1.3.1 (see Requirements for more information). Most likely, this is not the place you want to look for information. Please turn to the main spray site at http://spray.io for more information about other available versions.

. . .

In the section about constructing Routes the ~ operator was introduced, which connects two routes in a way that allows a second route to get a go at a request if the first route "rejected" it. The concept of "rejections" is used by spray for maintaining a more functional overall architecture and in order to be able to properly handle all kinds of error scenarios.

When a filtering directive, like the get directive, cannot let the request pass through to its inner route because the filter condition is not satisfied the directive doesn't immediately complete the request with an error response. Doing so would make it impossible for other routes chained in after the failing filter to get a chance to handle the request. Rather, failing filters "reject" the request (the same happens when a route explicitly calls requestContext.reject(...)).

After having been rejected by a route the request will continue to flow through the routing structure and possibly find another route that can complete it. If there are more rejections all of them will be picked up and collected. If, in the end, the request could not be completed the HttpService will receive a set of rejections from its main route. It is then the job of the RejectionHandler in the HttpService to create an appropriate error response for the received rejections.

The Routing Tree

Essentially, when you combine directives and custom routes via nesting and the ~ operator, you build a routing structure that forms a tree. When a request comes in it is injected into this tree at the root and flows down through all the branches in a depth-first manner until either some node completes it or it is fully rejected.

Consider this schematic example:

val route =
    a {
      b {
        c {
          ... // route 1
        } ~
        d {
          ... // route 2
        } ~
        ... // route 3
      } ~
      e {
        ... // route 4
      }
    }

Here five directives form a routing tree. Route 1 will only be reached if directives a, b and c all let the request pass through. Route 2 will run if a and b pass, c rejects and d passes. Route 3 will run if a and b pass, but c and d reject. Route 3 can therefore be seen as a "catch-all" route that only kicks in, if routes chained into preceding positions reject. This mechanism can make complex filtering logic quite easy to implement: simply put the most specific cases up front and the most general cases in the back.