Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

First Steps with REST, Spray and Scala

DZone's Guide to

First Steps with REST, Spray and Scala

· Java Zone
Free Resource

What every Java engineer should know about microservices: Reactive Microservices Architecture.  Brought to you in partnership with Lightbend.

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":
Screen Shot 2014-11-11 at 11.13.10.png

Call with media-type "application/vnd.type.b":
Screen Shot 2014-11-11 at 11.13.31.png

Call with media-type "application/vnd.type.c":
Screen Shot 2014-11-11 at 11.13.47.png

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.

Microservices for Java, explained. Revitalize your legacy systems (and your career) with Reactive Microservices Architecture, a free O'Reilly book. Brought to you in partnership with Lightbend.

Topics:

Published at DZone with permission of Jos Dirksen, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}