Skip to content
This repository has been archived by the owner on Apr 24, 2024. It is now read-only.
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.

. . .

Routes are the central elements of all web services built with spray. In spray a route is defined like this:

type Route = RequestContext => Unit

It's a simple alias for a function taking a RequestContext as parameter.

Contrary to what you might initially expect a route does not return anything. Rather, all response processing (i.e. everything that needs to be done after your code has handled a request) is performed in "continuation-style" via the "responder" of the RequestContext. If you don't know what this means, don't worry. It'll become clear soon. The key point is that this design has the advantage of being completely non-blocking as well as actor-friendly since, this way, it's possible to simply send off the RequestContext to another actor in a "fire-and-forget" manner, without having to worry about results handling.

Generally when a route receives a request (or rather a RequestContext for it) it can do one of three things:

  1. Complete the request by calling requestContext.complete(...)
  2. Reject the request by calling requestContext.reject(...)
  3. Ignore the request (i.e. neither complete nor reject it)

The first case is pretty clear, by calling complete the suspended request is resumed and a specific response is sent to the client. In the second case "reject" means that the route does not want to handle the request. You'll see further down in the section about route composition what this is good for. The third case is usually an error. If the route does not do anything with the request it will simply not be acted upon. This means that the client will not receive a response until the suspended request times out at which point a 500 Internal Server Error response will be generated. Therefore your routes should usually end up either completing or rejecting the request. One exception could be a scenario where you have two HttpService actors of which one performs the normal request processing and the second does some other, internal thing (like request logging). In this case it's ok when the routes of the second service do not complete or reject the request since you know that another HttpService will take care of this.

Constructing Routes

Since routes are ordinary functions RequestContext => Unit, the simplest route is

ctx => ctx.complete("Response")

or shorter:

_.complete("Response")

which simply completes all requests with a static response.

Even though you could write all your application logic as one monolithic function that inspects the RequestContext and completes it depending on its properties this type of design would be hard to read, maintain and reuse.

Therefore spray allows you to construct more complex routes from simpler ones through composition. There are three basic operations we need for this:

  • Route transformation, which delegates processing to another "inner" route but in the process changes some properties of either the incoming request, the outgoing response or both
  • Route filtering, which only lets requests satisfying a given filter condition pass and rejects all others
  • Route chaining, which tries a second route if a given first one was rejected

The last point is achieved with the simple operator '~', which is available to all routes via a "pimp", i.e. an implicit "extension". The first two points are provided by a large number of so-called "directives" which are built into spray and deliver most of its power and flexibility.