Getting Some REST With Spray
Nimrod Argov
–spray.io
“Open source toolkit for building REST/HTTP-based
integration layers on top of Scala and Akka”
Another HTTP Framework?
Netty
Servlets
Undertow
Play!
…
Sure we can.
Is it good enough?
Things We
Don’t Want
Containers
XML
Mutability
Java under the surface
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web
Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<servlet>
<servlet-name>controlServlet</servlet-name>
<servlet-class>
com.jenkov.butterfly.ControlServlet
</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>controlServlet</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
</web-app>
Not a
Framework
A set of libraries for http
integration
Testable
and not just e2e
Scala
Case Classes
Functions as Values
Collections
Type Safety
Akka
Concurrency Model
Actors
Unified Management
Spray Under the Hood
Scala
Akka Actors & Futures
Akka I/O (used to be spray-io)
Thread Pool / Config / Logging by Akka
Main Modules
Server DSL (spray-routing)
Client DSL (spray-client)
Low-level HTTP Server/Client (spray-can)
HTTP Data Model (spray-http)
Test Kit (spray-testkit)
Spray Routing
Hello, World!
val myRoute =

path("hello"){

complete {

"world"

}

}
object MyServiceActor extends App with SimpleRoutingApp with MyService {

implicit val system = ActorSystem("MyService-System")



startServer(interface = "localhost", port = 9090)(myRoute)

}

Routes
type Route = RequestContext => Unit
ctx => ctx.complete(“response”)
complete("response")
Route Composition
Transform request / response for inner nested routes
Filter requests for certain routes by
Method
Path
Params
…
Route Chaining - Try a different route
Directives
Context Transformations
Filtering
Value Extraction
Request Completion
Directives - Chaining
val route =
a {
b {
c {
... // route 1
} ~
d {
... // route 2
} ~
... // route 3
} ~
e {
... // route 4
}
}
Directives - Filtering
val myRoute =

path("hello") {

get {

complete {

"world"

}

}

} ~

path("hi") {

post {

complete("Hello World!")

}

}
Directives - Extraction
val myRoute =

path("student" / IntNumber){ id =>

complete(s"student id received: $id")

}
Directives - Composition
val getOrPost = get | post
val myRoute =
(path("test") & getOrPost) {

complete("This could have been a get or a post only")

}
Directives - Composition
val teacher = "teacher" / IntNumber



val course = "course" / IntNumber



val teacherCourse = path(teacher / course)

val myRoute =
teacherCourse { (teacherId, courseId) =>

complete(s"Got teacher $teacherId and course $courseId")

}
Directives - Composition
val teacher = "teacher" / IntNumber



val course = "course" / IntNumber



val teacherCourse = path(teacher / course)

val myRoute =
(teacherCourse & getOrPost){ (teacherId, courseId) =>

complete(s"Got teacher $teacherId and course $courseId")

}
Directives - Composition
val teacher = "teacher" / IntNumber



val course = "course" / IntNumber



val teacherCourse = path(teacher / course)

val getOrPostTeacherCourse = teacherCourse & getOrPost
val myRoute =
getOrPostTeacherCourse { (teacherId, courseId) =>

complete(s"Got teacher $teacherId and course $courseId")

}
Directives - Type Safety
val teacher = "teacher" / IntNumber
(teacher | get){ teacherId =>

complete(s"Got teacher $teacherId")

}

DOES NOT COMPILE!
Directives - Many Variations
path("hi") {

post {

parameter("param".as[Int]) { param =>

validate(param > 0, "Sorry, param has to be greater than zero"){

complete("Hello World!" * param)

}

}

}

}
Rejections
val noSuchPageHandler = RejectionHandler {

case Nil => complete(NotFound, "Sorry, it's not here!")

}

val myRoute =
getOrPostTeacherCourse { (teacherId, courseId) =>

handleRejections(noSuchPageHandler) {

complete(s"Got teacher $teacherId and course $courseId")

}

}
Rejections - Selective
implicit val generalRejectionHandler = RejectionHandler {

case MissingQueryParamRejection("id") :: _ => 

complete(BadRequest, "Looks like a bug to me!")

}
Asynchronous Processing
class CalculatingActor extends Actor {

override def receive = {

case _ => sender ! 42

}

}
val calculator: ActorRef = 

actorRefFactory.actorOf(Props[CalculatingActor])
path("calc") {

complete{

(calculator ? "calc").

mapTo[Int].

map(result => s"calculation result is $result")

}

}
Testing
TestKit
Support for specs2 and Scalatest
No need for the actual server
Does use an actor system
TestKit
REQUEST ~> ROUTE ~> check {

ASSERTIONS

}

TestKit
class RouteTest extends Specification with Specs2RouteTest
with MyService{


def actorRefFactory = system



"My REST service" should {

"Answer a hello message" in {

Get("/hello") ~> myRoute ~> check {

responseAs[String] === "world"

}

}
}
}
TestKit
class RouteTest extends Specification with Specs2RouteTest
with MyService{


def actorRefFactory = system



"My REST service" should {

"Reject a POST request" in {

Post("/hello") ~> myRoute ~> check {

rejection === MethodRejection(HttpMethods.GET)

}

}

}

}

TestKit
class RouteTest extends Specification with Specs2RouteTest
with MyService{


def actorRefFactory = system



"My REST service" should {

"Reject a POST request" in {

Post("/hello") ~> sealRoute(myRoute) ~> check {

status === MethodNotAllowed

}

}

}

}

Future[Spray]?
Akka-HTTP
Spray 2.0
Polishing of APIs
Java APIs
Based on Reactive Streams
Akka-HTTP
Better API for handling chunked messages
Client Overhaul
Websockets support (spray doesn’t support out of the
box)
References
http://spray.io/documentation/
https://github.com/spray/spray/tree/master/examples
nimroda at wix dot com
@nimrodargov

Rest with-spray