First steps with REST, Spray and Scala
On this site you can already find a couple of articles on how to do REST with a multiple of different frameworks. You can find an old one on Play, some on Scalatra and I even started an (as of yet unfinished) series on Express. So instead of finishing the series on Express, I’m going to look at Spray in this article.
Getting started
First thing we need to do is get the correct libraries set up, so we can start development (I use IntelliJ IDEA, but you can use whatever you want). The easiest way to get started is by using SBT. I’ve used the following minimal SBT file to get started.
organization := "org.smartjava" version := "0.1" scalaVersion := "2.11.2" scalacOptions := Seq("-unchecked", "-deprecation", "-encoding", "utf8") libraryDependencies ++= { val akkaV = "2.3.6" val sprayV = "1.3.2" Seq( "io.spray" %% "spray-can" % sprayV withSources() withJavadoc(), "io.spray" %% "spray-routing" % sprayV withSources() withJavadoc(), "io.spray" %% "spray-json" % "1.3.1", "io.spray" %% "spray-testkit" % sprayV % "test", "com.typesafe.akka" %% "akka-actor" % akkaV, "com.typesafe.akka" %% "akka-testkit" % akkaV % "test", "org.specs2" %% "specs2-core" % "2.3.11" % "test", "org.scalaz" %% "scalaz-core" % "7.1.0" ) }
After you’ve imported this file into your IDE of choice you should have the correct spray and akka libraries to get started.
Create a launcher
Next lets create a launcher which you can use to run our Spray server. For this we just an object, creativeally named Boot, which extends from the standard scala App trait.
package org.smartjava; import akka.actor.{ActorSystem, Props} import akka.io.IO import spray.can.Http import akka.pattern.ask import akka.util.Timeout import scala.concurrent.duration._ object Boot extends App { // create our actor system with the name smartjava implicit val system = ActorSystem("smartjava") val service = system.actorOf(Props[SJServiceActor], "sj-rest-service") // IO requires an implicit ActorSystem, and ? requires an implicit timeout // Bind HTTP to the specified service. implicit val timeout = Timeout(5.seconds) IO(Http) ? Http.Bind(service, interface = "localhost", port = 8080) }
There isn’t happening that much in this object. What we do is we send a HTTP.Bind() message (better said we ‘ask’) to register a listener. If binding succeeds our service will receive messages whenever a request is received on the port.
Receiving actor
Now lets look at the actor where we’ll be sending the messages from the IO subsystem to.
package org.smartjava import akka.actor.Actor import spray.routing._ import spray.http._ import MediaTypes._ import spray.httpx.SprayJsonSupport._ import MyJsonProtocol._ // simple actor that handles the routes. class SJServiceActor extends Actor with HttpService { // required as implicit value for the HttpService // included from SJService def actorRefFactory = context // we don't create a receive function ourselve, but use // the runRoute function from the HttpService to create // one for us, based on the supplied routes. def receive = runRoute(aSimpleRoute ~ anotherRoute) // some sample routes val aSimpleRoute = {...} val anotherRoute = {...}
So what happens here is that when we use the runRoute function, provided by HttpService, to create the receive function that handles the incoming messages.
creating routes
The final step we need to configure is create some route handling code. We’ll go into more detail for this part in one of the next articles, so for now we’ll show you how to create a route that based on the incoming media-type sends back some JSON. We’ll use the standard JSON support from Spray for this. As a JSON object we’ll use the following very basic case class which we extended with JSON support.
package org.smartjava import spray.json.DefaultJsonProtocol object MyJsonProtocol extends DefaultJsonProtocol { implicit val personFormat = jsonFormat3(Person) } case class Person(name: String, fistName: String, age: Long)
This way Spray will marshall this object to JSON when we set the correct response media-type. Now that we’ve got our response object lets look at the code for the routes:
// handles the api path, we could also define these in separate files // this path respons to get queries, and make a selection on the // media-type. val aSimpleRoute = { path("path1") { get { // Get the value of the content-header. Spray // provides multiple ways to do this. headerValue({ case x@HttpHeaders.`Content-Type`(value) => Some(value) case default => None }) { // the header is passed in containing the content type // we match the header using a case statement, and depending // on the content type we return a specific object header => header match { // if we have this contentype we create a custom response case ContentType(MediaType("application/vnd.type.a"), _) => { respondWithMediaType(`application/json`) { complete { Person("Bob", "Type A", System.currentTimeMillis()); } } } // if we habe another content-type we return a different type. case ContentType(MediaType("application/vnd.type.b"), _) => { respondWithMediaType(`application/json`) { complete { Person("Bob", "Type B", System.currentTimeMillis()); } } } // if content-types do not match, return an error code case default => { complete { HttpResponse(406); } } } } } } } // handles the other path, we could also define these in separate files // This is just a simple route to explain the concept val anotherRoute = { path("path2") { get { // respond with text/html. respondWithMediaType(`text/html`) { complete { // respond with a set of HTML elements <html> <body> <h1>Path 2</h1> </body> </html> } } } } }
A lot of code is in there, so lets highlight a couple of elements in detail:
val aSimpleRoute = { path("path1") { get {...} } }
This starting point of the route first checks whether the request is made to the “localhost:8080/path1” path and then checks the HTTP method. In this case we’re only interested in GET methods. Once we’ve got a get method we do the following:
// Get the value of the content-header. Spray // provides multiple ways to do this. headerValue({ case x@HttpHeaders.`Content-Type`(value) => Some(value) case default => None }) { // the header is passed in containing the content type // we match the header using a case statement, and depending // on the content type we return a specific object header => header match { // if we have this contentype we create a custom response case ContentType(MediaType("application/vnd.type.a"), _) => { respondWithMediaType(`application/json`) { complete { Person("Bob", "Type A", System.currentTimeMillis()); } } } // if we habe another content-type we return a different type. case ContentType(MediaType("application/vnd.type.b"), _) => { respondWithMediaType(`application/json`) { complete { Person("Bob", "Type B", System.currentTimeMillis()); } } } // if content-types do not match, return an error code case default => { complete { HttpResponse(406); } } } } }
In this piece of code we extract the Content-Type header of the request and based on that determine the response. The response is automatically converted to JSON because the responseWithMediaType is set to application/json. If a mediatype is provided which we don’t understand we return an 406 message.
Lets test this
Now lets test whether this is working. Spray provides own libraries and classes for testing, but for now lets just use a simple basic rest client. For this I usually use the Chrome Advanced Rest Client. In the following two screenshots you can see three calls being made to http://localhost:8080/path1:
Call with media-type “application/vnd.type.a”:
Call with media-type “application/vnd.type.b”:
Call with media-type “application/vnd.type.c”:
As you can see, the responses exactly match the routes we defined.
What is next
In the following article we’ll connect Spray IO to a database, make testing a little bit easier and explore a number of other Spray.IO features.
Reference: | First steps with REST, Spray and Scala from our JCG partner Jos Dirksen at the Smart Java blog. |
Please publish zipped project or provide a ling to git.
Thanks!
I am getting the following error
“type mismatch; found : com.example.Person required: spray.httpx.marshalling.ToResponseMarshallable”
is there any configuration i am missing
Make sure you have imported:
import spray.httpx.SprayJsonSupport._
import MyJsonProtocol._
in SJServiceActor
There are implicits declared which are used for this convertion.
You have not provided how to actually run this project from intellij