In the previous blog post I introduced Spray and the Actor Pattern. In that post I put together a simple actor that handled parsing a Regex pattern into a state transition diagram and table. Today, we're going to look at how we can use the Spray framework to build a JSON web service to expose this operation to the world.
Routing In Spray
Routing enables us to deal with the different web requests that are made to our server. When we receive a web request to our server, we can look at the path that is provided to us, and make a decision on what our response should be, based on what is requested.
Routing in Spray is implemented using a domain-specific language (DSL) that is cleverly implemented in vanilla Scala code. The way the DSL works is rather complex (you can read about it here), and for our purposes, it's easiest to treat it as a specific DSL for Spray routing and focus on how it can be used to set up routes.
To introduce the DSL, let's look at an example.
Let's start with the first route:
path() function is referred to in Spray as a "directive". You can read a lot more about Spray directives here, but it is easiest to think of them as (as lifted directly from that page) small building blocks from which you can construct route structures.
The path directive takes a "path matcher" string to compare against the part of the URL that hasn't been matched yet. You can think of path matching as eating the parts of the path after the domain name (for example, in "app.jacobappleton.io/app/regex", the matching starts after the bolded domain name). In this first route, we're matching the empty string, which means we weren't given any resources to match, which means we're at the "root" of our application (in this case, I've hosted the application at app.jacobappleton.io and this route matches that URL).
There are lots of different directives in Spray, and you can see that in the first route we use the redirect directive. The redirect directive allows us to, as it says on the tin, redirect our request to a different URL. In this case, I don't have anything that I wish to be hosted at the URL yet, so we'll just redirect the users back to my blog instead.
The Application Route
The path prefix directive allows us to group multiple routes under a particular prefix and eats that prefix from the URL before passing it along to the sub-routes. In this case, we're matching the "app.jacobappleton.io/app/..." prefix and passing the rest of the URL along to the sub-routes.
Our first sub-route matches the URL "app.jacobappleton.io/app/assets/..." and uses the getFromResourcesDirectory directive to, when anything is requested on this route, respond with the equivalent file in the "web/assets" folder within the resources folder of our application. The resources folder allows us to store non-compiled files in our application binary output, and this is a perfect place to store the files we need to serve to the user to run the application.
Otherwise, when anything else is requested under the app path, we respond with web/index.html. This is an EmberJS application that I'll explain further in future blog posts.
The API Route
The third and final route relates to our JSON API, and how we respond to requests to parse a Regex pattern.
Here we use the post directive, to handle HTTP POST requests to our API. At this point, I'm not sure if it makes more RESTful sense to use an HTTP GET, but this is a simple example and POST works for now.
We use the entity directive and the complete directive to obtain the request body as a String, and "ask" our RegexWorker actor to parse the Regex pattern. Finally, we map the result to a RegexResponse type that I discussed in the previous blog post.
mapTo comes from Akka and takes a future and returns a new future, that future will return the result if the cast was successful and an exception if the cast was not. You can read more about mapTo here. If you're not familiar with the futures-pattern, I'd recommend checking it out on Wikipedia.
Spray's complete directive is smart enough to understand futures, and if you provide a future that is marshallable (serialisable) back to a response, then the complete directive will automatically send the result back up the route structure and eventually to the caller.
That's the gist of routing, and there's a lot you can do with just that alone. Next time, we'll talk about marshalling and how to receive and accept complex data-types.